[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.
This commit is contained in:
Malte Bargholz
2020-10-05 17:25:33 +02:00
committed by Christian Dietrich
parent 86267cce9f
commit cd150a6f5b
4 changed files with 254 additions and 38 deletions

View File

@ -5,6 +5,7 @@
#include <cstring> // memcpy, cstring
#include <algorithm>
#include "Demangler.hpp"
#include <limits.h>
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<guest_address_t,guest_address_t>
ElfReader::getValidAddressBounds() {
guest_address_t min = std::numeric_limits<guest_address_t>::max();
guest_address_t max = std::numeric_limits<guest_address_t>::min();
for(const auto& seg: m_segmenttable) {
min = std::min(min, seg.getStart());
max = std::max(max, seg.getEnd());
}
if (min == std::numeric_limits<guest_address_t>::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<guest_address_t>::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<guest_address_t,guest_address_t>
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

View File

@ -4,11 +4,14 @@
#include <string>
#include <ostream>
#include <vector>
#include <utility>
#include <map>
#include <elf.h>
#include "sal/SALConfig.hpp" // for ADDR_INV
#include "Logger.hpp"
#include "Demangler.hpp"
#include <string.h>
#include <cassert>
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_entry_t> 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<guest_address_t, guest_address_t> getValidAddressBounds();
/**
* Return minimal and maximal address in the ELF's text segment
*/
std::pair<guest_address_t, guest_address_t> 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

View File

@ -17,6 +17,8 @@
#include "sal/bochs/BochsListener.hpp"
#include <string>
#include <vector>
#include <stdexcept>
#include <limits>
#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<guest_address_t>::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<guest_address_t>::min());
l_mem_lowerspace.setWatchWidth(bounds.first - numeric_limits<guest_address_t>::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<GenericExperimentMessage_Result *>(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());

View File

@ -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;