From cd150a6f5b1699d35459491c77a0f046aeb0b4e6 Mon Sep 17 00:00:00 2001 From: Malte Bargholz Date: Mon, 5 Oct 2020 17:25:33 +0200 Subject: [PATCH] [generic-experiment/ElfReader] access listeners based on ELF segments This patch uses ELF segments instead of section for determining the extent of the binary in memory. Consequently, the outerspace listener was modified to use these new bounds. Additionally, the generic experiment was changed to include a lowerspace listener, which listens for write access below the physical memory location of the ELF. --- src/core/util/ElfReader.cc | 107 +++++++++++++++++- src/core/util/ElfReader.hpp | 77 ++++++++++++- .../generic-experiment/experiment.cc | 104 +++++++++++------ .../generic-experiment/experiment.hpp | 4 + 4 files changed, 254 insertions(+), 38 deletions(-) diff --git a/src/core/util/ElfReader.cc b/src/core/util/ElfReader.cc index 1044f4b5..46a49615 100644 --- a/src/core/util/ElfReader.cc +++ b/src/core/util/ElfReader.cc @@ -5,6 +5,7 @@ #include // memcpy, cstring #include #include "Demangler.hpp" +#include namespace fail { @@ -64,6 +65,16 @@ void ElfReader::setup(const char* path) { << (m_elfclass == ELFCLASS32 ? "32-bit" : "64-bit") << " ELF file: " << path << std::endl; + // Parse ELF segments + Elf64_Phdr seg_hdr; + for(unsigned i = 0; i < ehdr.e_phnum; ++i) { + if(!read_ELF_segment_header(fp, &ehdr, i, &seg_hdr)) { + m_log << "Invalid segment header at index " << i << std::endl; + continue; + } + process_segment(&seg_hdr); + } + // Parse symbol table and generate internal map for (int i = 0; i < num_hdrs; ++i) { if (!read_ELF_section_header(fp, &ehdr, i, &sec_hdr)) { @@ -109,12 +120,20 @@ void ElfReader::setup(const char* path) { int ElfReader::process_section(Elf64_Shdr *sect_hdr, char *sect_name_buff) { // Add section name, start address and size to list int idx=sect_hdr->sh_name; - // m_sections_map.push_back( sect_hdr->sh_addr, sect_hdr->sh_size, sect_name_buff+idx ); + // m_sections_map.push_back( sect_hdr->sh_addr, sect_hdr->sh_size, sect_name_buff+idx ); m_sectiontable.push_back( ElfSymbol(sect_name_buff+idx, sect_hdr->sh_addr, sect_hdr->sh_size, ElfSymbol::SECTION) ); return 0; } +void ElfReader::process_segment(Elf64_Phdr *seg_hdr) { + // FIXME: this does not handle the PT_PHDR segment type where the + // program header is actually located somewhere else. + if(seg_hdr->p_type == PT_LOAD) { + m_segmenttable.emplace_back(seg_hdr); + } +} + static void Elf32to64_Sym(Elf32_Sym const *src, Elf64_Sym *dest) { dest->st_name = src->st_name; @@ -142,7 +161,7 @@ bool ElfReader::process_symboltable(FILE *fp, Elf64_Ehdr const *ehdr, int sect_n link = sect_hdr.sh_link; sym_data_offset = sect_hdr.sh_offset; sym_data_size = sect_hdr.sh_size; - num_sym = sym_data_size / + num_sym = sym_data_size / (m_elfclass == ELFCLASS32 ? sizeof(Elf32_Sym) : sizeof(Elf64_Sym)); // read the corresponding strtab @@ -281,6 +300,37 @@ bool ElfReader::read_ELF_section_header(FILE *fp, Elf64_Ehdr const *filehdr, int return true; } +static void Elf32to64_Phdr(Elf32_Phdr const *src, Elf64_Phdr *dest) { + dest->p_type = src->p_type; + dest->p_offset = src->p_offset; + dest->p_vaddr = src->p_vaddr; + dest->p_paddr = src->p_paddr; + dest->p_filesz = src->p_filesz; + dest->p_memsz = src->p_memsz; + dest->p_flags = src->p_flags; + dest->p_align = src->p_align; +} + +bool ElfReader::read_ELF_segment_header(FILE* fp, Elf64_Ehdr const * filehdr, unsigned segment, Elf64_Phdr * seg_hdr) { + if(filehdr->e_phnum < segment) return false; + off_t phdr_offset = filehdr->e_phoff; + phdr_offset += filehdr->e_phentsize*segment; + fseek(fp, phdr_offset, SEEK_SET); + if(m_elfclass == ELFCLASS32) { + Elf32_Phdr seg_hdr32; + if(!fread(&seg_hdr32, sizeof(seg_hdr32), 1, fp)){ + return false; + } + Elf32to64_Phdr(&seg_hdr32,seg_hdr); + } else { + if(!fread(seg_hdr, sizeof(*seg_hdr), 1, fp)) { + return false; + } + } + + return true; +} + const ElfSymbol& ElfReader::getSymbol(guest_address_t address) { for (container_t::const_iterator it = m_symboltable.begin(); it !=m_symboltable.end(); ++it) { if (it->contains(address)) { @@ -341,7 +391,7 @@ void ElfReader::printDemangled() { if (str == Demangler::DEMANGLE_FAILED) { str = it->getName(); } - m_log << "0x" << std::hex << it->getAddress() << "\t" << str.c_str() << "\t" << it->getSize() << std::endl; + m_log << "0x" << std::hex << it->getAddress() << "\t" << str.c_str() << "\t" << it->getSize() << std::endl; } } @@ -353,8 +403,57 @@ void ElfReader::printMangled() { void ElfReader::printSections() { for (container_t::const_iterator it = m_sectiontable.begin(); it !=m_sectiontable.end(); ++it) { - m_log << "0x" << it->getAddress() << "\t" << it->getName().c_str() << "\t" << it->getSize() << std::endl; + m_log << "0x" << it->getAddress() << "\t" << it->getName().c_str() << "\t" << it->getSize() << std::endl; } } +void ElfReader::printSegments() { + for(auto it = m_segmenttable.begin(); it != m_segmenttable.end(); ++it) { + m_log << *it << std::endl; + } +} + +std::pair +ElfReader::getValidAddressBounds() { + guest_address_t min = std::numeric_limits::max(); + guest_address_t max = std::numeric_limits::min(); + + for(const auto& seg: m_segmenttable) { + min = std::min(min, seg.getStart()); + max = std::max(max, seg.getEnd()); + } + if (min == std::numeric_limits::max()) + throw new std::invalid_argument("invalid minimum ELF address, either the segment header are invalid or the ELF will take no space in memory."); + if (max == std::numeric_limits::min()) + throw new std::invalid_argument("invalid maximum ELF address, either the segment header are invalid or the ELF will take no space in memory."); + + return std::make_pair(min, max); +} + +std::pair +ElfReader::getTextSegmentBounds() { + address_t minimal_ip; + address_t maximal_ip; + bool found_executable_segment = false; + + for (const auto &seg : m_segmenttable) { + if(seg.isExecutable()) { + if(found_executable_segment) { + m_log << "warning: found multiple executable segments in ELF, we will only detect writes to the first executable segment!" << std::endl; + continue; + } + found_executable_segment = true; + minimal_ip = seg.getStart(); + maximal_ip = seg.getEnd(); + } + } + + if (!found_executable_segment) + throw new std::invalid_argument("ELF file has no executable segment. aborting!"); + + return std::make_pair(minimal_ip, maximal_ip); +} + + + } // end-of-namespace fail diff --git a/src/core/util/ElfReader.hpp b/src/core/util/ElfReader.hpp index d6712b93..41f8bd00 100644 --- a/src/core/util/ElfReader.hpp +++ b/src/core/util/ElfReader.hpp @@ -4,11 +4,14 @@ #include #include #include +#include #include #include #include "sal/SALConfig.hpp" // for ADDR_INV #include "Logger.hpp" #include "Demangler.hpp" +#include +#include namespace fail { @@ -16,6 +19,42 @@ struct ELF { static const std::string NOTFOUND; }; +class ElfSegment { + private: + guest_address_t m_paddress; + guest_address_t m_vaddress; + size_t m_size; + unsigned m_flags; + + public: + ElfSegment(Elf64_Phdr const * hdr) + : m_paddress(hdr->p_paddr), + m_vaddress(hdr->p_vaddr), + m_size(hdr->p_memsz), + m_flags(hdr->p_flags) { } + + bool isReadable() const { return m_flags & PF_R; } + bool isWriteable() const { return m_flags & PF_W; } + bool isExecutable() const { return m_flags & PF_X; } + guest_address_t getStart() const { assert(m_paddress == m_vaddress); return m_paddress; } + guest_address_t getEnd() const { assert(m_paddress == m_vaddress); return m_paddress + m_size; } + guest_address_t getSize() const { assert(m_paddress == m_vaddress); return m_size; } + friend std::ostream& operator << (std::ostream&,const ElfSegment&); +}; + +inline std::ostream& operator<< (std::ostream &out, const ElfSegment &s) { + return out << std::hex << std::showbase + << "Elf Segment @ physical: " << s.m_paddress + << " virtual: " << s.m_vaddress + << " size: " << s.m_size + << " (" + << (s.isReadable() ? "R": " ") + << (s.isWriteable() ? "W" : " ") + << (s.isExecutable() ? "X" : " ") + << ")" + << std::dec << std::noshowbase; +} + class ElfSymbol { std::string name; guest_address_t address; @@ -81,6 +120,10 @@ public: typedef container_t::const_iterator symbol_iterator; typedef container_t::const_iterator section_iterator; + typedef ElfSegment seg_entry_t; + typedef std::vector seg_container_t; + typedef seg_container_t::const_iterator segment_iterator; + int m_machine; int m_elfclass; @@ -112,6 +155,11 @@ public: */ void printSections(); + /** + * Print List of all available segments + */ + void printSegments(); + /** * Get symbol by address * @param address Address within range of the symbol @@ -154,6 +202,26 @@ public: container_t::const_iterator sec_begin() { return m_sectiontable.begin(); } container_t::const_iterator sec_end() { return m_sectiontable.end(); } + + + /** + * Get segment iterator. Dereferences to an ElfSymbol + * @return iterator + */ + seg_container_t::const_iterator seg_begin() { return m_segmenttable.begin(); } + seg_container_t::const_iterator seg_end() { return m_segmenttable.end(); } + + /** + * Return minimal and maximal valid address in ELF, this uses the ELF segment headers + * and assumes that there are no holes in the ELFs memory layout. + */ + std::pair getValidAddressBounds(); + + /** + * Return minimal and maximal address in the ELF's text segment + */ + std::pair getTextSegmentBounds(); + const std::string & getFilename() { return m_filename; } private: @@ -163,17 +231,24 @@ private: void setup(const char*); bool process_symboltable(FILE *fp, Elf64_Ehdr const *ehdr, int sect_num); int process_section(Elf64_Shdr *sect_hdr, char *sect_name_buff); + void process_segment(Elf64_Phdr *seg_hdr); - // Returns true if it finds a valid ELF header. Stores ELFCLASS32 or 64 in m_elfclass. + // Returns true if it finds a valid ELF header. Stores ELFCLASS32 or 64 in m_elfclass. bool read_ELF_file_header(FILE *fp, Elf64_Ehdr *ehdr); + // Returns true if it finds a valid ELF section header. bool read_ELF_section_header(FILE *fp, Elf64_Ehdr const *filehdr, int sect_num, Elf64_Shdr *sect_hdr); + // Return true if it finds a valid ELF program header + bool read_ELF_segment_header(FILE* fp, Elf64_Ehdr const *filehdr, unsigned seg_num, Elf64_Phdr* seg_hdr); + container_t m_symboltable; container_t m_sectiontable; + seg_container_t m_segmenttable; guest_address_t getAddress(const std::string& name); std::string getName(guest_address_t address); + }; } // end-of-namespace fail diff --git a/src/experiments/generic-experiment/experiment.cc b/src/experiments/generic-experiment/experiment.cc index b4e227ca..506f0570 100644 --- a/src/experiments/generic-experiment/experiment.cc +++ b/src/experiments/generic-experiment/experiment.cc @@ -17,6 +17,8 @@ #include "sal/bochs/BochsListener.hpp" #include #include +#include +#include #include "campaign.hpp" #include "generic-experiment.pb.h" @@ -117,6 +119,10 @@ bool GenericExperiment::cb_start_experiment() { = cmd.addOption("", "catch-write-outerspace", Arg::None, "--catch-write-outerspace \tCatch writes to the outerspace"); + CommandLine::option_handle WRITE_MEM_LOWERSPACE + = cmd.addOption("", "catch-write-lowerspace", Arg::None, + "--catch-write-lowerspace \tCatch writes to the lowerspace"); + CommandLine::option_handle TIMEOUT = cmd.addOption("", "timeout", Arg::Required, "--timeout TIME \tExperiment timeout in uS"); @@ -166,24 +172,6 @@ bool GenericExperiment::cb_start_experiment() { exit(1); } - address_t minimal_ip = INT_MAX; // Every address is lower - address_t maximal_ip = 0; - address_t minimal_data = 0x100000; // 1 Mbyte - address_t maximal_data = 0; - - for (ElfReader::section_iterator it = m_elf->sec_begin(); - it != m_elf->sec_end(); ++it) { - const ElfSymbol &symbol = *it; - std::string prefix(".text"); - if (symbol.getName().compare(0, prefix.size(), prefix) == 0) { - minimal_ip = std::min(minimal_ip, symbol.getStart()); - maximal_ip = std::max(maximal_ip, symbol.getEnd()); - } else { - minimal_data = std::min(minimal_data, symbol.getStart()); - maximal_data = std::max(maximal_data, symbol.getEnd()); - } - } - if (cmd[SERIAL_PORT]) { option::Option *opt = cmd[SERIAL_PORT].first(); char *endptr; @@ -223,22 +211,41 @@ bool GenericExperiment::cb_start_experiment() { sol.setLimit(serial_goldenrun.size() + 1); } - if (cmd[WRITE_MEM_TEXT]) { - m_log << "Catch writes to text segment from " << hex << minimal_ip << " to " << maximal_ip << std::endl; - enabled_mem_text = true; - l_mem_text.setWatchAddress(minimal_ip); + if (cmd[WRITE_MEM_TEXT]) { + enabled_mem_text = true; + auto bounds = m_elf->getTextSegmentBounds(); + m_log << "Catch writes to text segment from " + << hex << bounds.first << " to " << bounds.second << std::endl; + + l_mem_text.setWatchAddress(bounds.first); + l_mem_text.setWatchWidth(bounds.second - bounds.first); l_mem_text.setTriggerAccessType(MemAccessEvent::MEM_WRITE); - l_mem_text.setWatchWidth(maximal_ip - minimal_ip); } + if (cmd[WRITE_MEM_OUTERSPACE]) { - m_log << "Catch writes to outerspace from " << hex << " from " << maximal_data << std::endl; enabled_mem_outerspace = true; - l_mem_outerspace.setWatchAddress(maximal_data); + auto bounds = m_elf->getValidAddressBounds(); + m_log << "Catch writes to upper outerspace from " << hex << bounds.second << std::endl; + + l_mem_outerspace.setWatchAddress(bounds.second); + l_mem_outerspace.setWatchWidth(numeric_limits::max() - bounds.second); l_mem_outerspace.setTriggerAccessType(MemAccessEvent::MEM_WRITE); - l_mem_outerspace.setWatchWidth(0xfffffff0 - maximal_data); + } + + if (cmd[WRITE_MEM_LOWERSPACE]) { + enabled_mem_lowerspace = true; + + auto bounds = m_elf->getValidAddressBounds(); + m_log << "Catch writes to lower outer-space below " << hex << bounds.first << std::endl; + + // FIXME: this might not work if your benchmark uses any devices mapped below the actual ELF + // however, this is not the case for RISC-V and consequently, it is ignored here. + l_mem_lowerspace.setWatchAddress(numeric_limits::min()); + l_mem_lowerspace.setWatchWidth(bounds.first - numeric_limits::min()); + l_mem_lowerspace.setTriggerAccessType(MemAccessEvent::MEM_WRITE); } if (cmd[TRAP]) { @@ -309,6 +316,10 @@ bool GenericExperiment::cb_before_resume() { std::cout << "enabled mem outerspace " << endl; simulator.addListener(&l_mem_outerspace); } + if (enabled_mem_lowerspace) { + std::cout << "enabled mem lowerspace " << endl; + simulator.addListener(&l_mem_lowerspace); + } if (enabled_timeout) simulator.addListener(&l_timeout); @@ -321,6 +332,8 @@ bool GenericExperiment::cb_before_resume() { return true; // everything OK } + + void GenericExperiment::cb_after_resume(fail::BaseListener *event) { GenericExperimentMessage_Result * result = static_cast(this->get_current_result()); @@ -331,18 +344,43 @@ void GenericExperiment::cb_after_resume(fail::BaseListener *event) { handleEvent(*result, result->TIMEOUT, m_Timeout); } else if (event == &l_trap) { handleEvent(*result, result->TRAP, l_trap.getTriggerNumber()); - } else if (event == &l_mem_text) { + } + //////////////////////////////////////////////////////////////// + // Memory Access Listeners + //////////////////////////////////////////////////////////////// +#define LOG_MEM_LISTENER(l) " @ 0x" \ + << std::hex << (l).getTriggerAddress() \ + << " access type trigger: " \ + << ((l).getTriggerAccessType() == MemAccessEvent::MEM_READ ?"R":"W") + // FIXME: Add after memory types are introduced + // << " memory type watched: " << l_mem_lowerspace.getWatchMemoryType() + // << " memory type trigger: " << l_mem_lowerspace.getMemoryType() + // << " data accessed: " << (l).getAccessedData() + + else if (event == &l_mem_text) { + m_log << "memory text-write triggered" + << LOG_MEM_LISTENER(l_mem_text) + << std::endl; handleEvent(*result, result->WRITE_TEXTSEGMENT, l_mem_text.getTriggerAddress()); - } else if (event == &l_mem_outerspace){ + m_log << "memory upper outerspace triggered" + << LOG_MEM_LISTENER(l_mem_outerspace) + << std::endl; handleEvent(*result, result->WRITE_OUTERSPACE, l_mem_outerspace.getTriggerAddress()); - - ////////////////////////////////////////////////// - // End Marker Groups - ////////////////////////////////////////////////// - } else if (OK_marker.find(event) != OK_marker.end()) { + } else if (event == &l_mem_lowerspace) { + m_log << "memory lower outerspace triggered" + << LOG_MEM_LISTENER(l_mem_lowerspace) + << std::endl; + handleEvent(*result, result->WRITE_LOWERSPACE, + l_mem_lowerspace.getTriggerAddress()); + } +#undef LOG_MEM_LISTENER + ////////////////////////////////////////////////// + // End Marker Groups + ////////////////////////////////////////////////// + else if (OK_marker.find(event) != OK_marker.end()) { const ElfSymbol *symbol = listener_to_symbol[event]; handleEvent(*result, result->OK_MARKER, symbol->getAddress()); diff --git a/src/experiments/generic-experiment/experiment.hpp b/src/experiments/generic-experiment/experiment.hpp index c2883c8d..a53fedb5 100644 --- a/src/experiments/generic-experiment/experiment.hpp +++ b/src/experiments/generic-experiment/experiment.hpp @@ -31,6 +31,9 @@ class GenericExperiment : public fail::DatabaseExperiment { bool enabled_mem_outerspace; fail::MemAccessListener l_mem_outerspace; + bool enabled_mem_lowerspace; + fail::MemAccessListener l_mem_lowerspace; + bool enabled_trap; fail::TrapListener l_trap; @@ -62,6 +65,7 @@ public: l_trap(fail::ANY_TRAP), l_timeout(0) { enabled_mem_text = false; enabled_mem_outerspace = false; + enabled_mem_lowerspace = false; enabled_trap = false; enabled_timeout = false;