mirror of
https://github.com/QuasarApp/LIEF.git
synced 2025-04-28 13:24:32 +00:00
Merge branch 'pr/176'
This commit is contained in:
commit
4cb7ba46c7
@ -75,6 +75,9 @@ class LIEF_API Builder {
|
||||
template<typename ELF_T>
|
||||
void build_pltgot_relocations(void);
|
||||
|
||||
template<typename ELF_T>
|
||||
void build_section_relocations(void);
|
||||
|
||||
template<typename ELF_T>
|
||||
void build_hash_table(void);
|
||||
|
||||
|
@ -189,7 +189,7 @@ class LIEF_API Parser : public LIEF::Parser {
|
||||
//! use parse relocations by using LIEF::ELF::Segment. This method parse relocations
|
||||
//! that are not reachable through segments (For example Object file).
|
||||
template<typename ELF_T, typename REL_T>
|
||||
void parse_section_relocations(uint64_t offset, uint64_t size, Section *applies_to = nullptr);
|
||||
void parse_section_relocations(Section const& section);
|
||||
|
||||
//! @brief Parse SymbolVersionRequirement
|
||||
//!
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include "Object.tcc"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace LIEF {
|
||||
namespace ELF {
|
||||
|
||||
@ -117,6 +119,13 @@ void Builder::build(void) {
|
||||
}
|
||||
}
|
||||
|
||||
if (this->binary_->object_relocations().size() > 0) {
|
||||
try {
|
||||
this->build_section_relocations<ELF_T>();
|
||||
} catch (const LIEF::exception& e) {
|
||||
LOG(ERROR) << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// Build sections
|
||||
if (this->binary_->sections_.size() > 0) {
|
||||
@ -223,7 +232,8 @@ void Builder::build_sections(void) {
|
||||
using Elf_Shdr = typename ELF_T::Elf_Shdr;
|
||||
VLOG(VDEBUG) << "[+] Build sections";
|
||||
|
||||
const Header& header = this->binary_->header();
|
||||
// FIXME: Keep it global const and local non const
|
||||
Header& header = this->binary_->header();
|
||||
const Elf_Off section_headers_offset = header.section_headers_offset();
|
||||
|
||||
std::vector<std::string> stringTableOpti =
|
||||
@ -238,28 +248,56 @@ void Builder::build_sections(void) {
|
||||
}
|
||||
|
||||
Section* string_names_section = this->binary_->sections_[header.section_name_table_idx()];
|
||||
|
||||
auto&& it_symtab_section = std::find_if(
|
||||
std::begin(this->binary_->sections_),
|
||||
std::end(this->binary_->sections_),
|
||||
[] (const Section* section)
|
||||
{
|
||||
return section != nullptr and section->type() == ELF_SECTION_TYPES::SHT_SYMTAB;
|
||||
});
|
||||
|
||||
// If there is already a symtab section with a str_section that is the same
|
||||
// as the str_section of sections, create a new one for str_section of sections
|
||||
if (it_symtab_section != std::end(this->binary_->sections_)) {
|
||||
Section& symbol_section = **it_symtab_section;
|
||||
Section* symbol_str_section = nullptr;
|
||||
if (symbol_section.link() != 0 or
|
||||
symbol_section.link() < this->binary_->sections_.size()) {
|
||||
symbol_str_section = this->binary_->sections_[symbol_section.link()];
|
||||
}
|
||||
|
||||
if(symbol_str_section == string_names_section)
|
||||
{
|
||||
Section sec_str_section(".shstrtab", ELF_SECTION_TYPES::SHT_STRTAB);
|
||||
sec_str_section.content(section_names);
|
||||
|
||||
auto& new_str_section = this->binary_->add(sec_str_section, false);
|
||||
|
||||
auto it = std::find_if(std::begin(this->binary_->sections_),
|
||||
std::end(this->binary_->sections_),
|
||||
[&new_str_section](Section* S) {
|
||||
return S == &new_str_section;
|
||||
});
|
||||
assert(it != std::end(this->binary_->sections_));
|
||||
|
||||
// FIXME: We should remove the old section
|
||||
header.section_name_table_idx(std::distance(std::begin(this->binary_->sections_), it));
|
||||
|
||||
return this->build<ELF_T>();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Handle if we add sections names and we shoudl increase section size
|
||||
string_names_section->content(section_names);
|
||||
|
||||
// **Should** be safe since .shstr is located at the end of the binary
|
||||
//if (string_names_section->size() < section_names.size()) {
|
||||
// string_names_section = &(this->binary_->extend_section(*string_names_section, section_names.size() - string_names_section->size() + 1));
|
||||
//}
|
||||
|
||||
// First write every section and then the header because if we do all of it
|
||||
// in a row, we will write the old header section after some new header so they
|
||||
// will be remove
|
||||
for (size_t i = 0; i < this->binary_->sections_.size(); i++) {
|
||||
const Section* section = this->binary_->sections_[i];
|
||||
VLOG(VDEBUG) << "Writing back '" << section->name() << "'";
|
||||
|
||||
auto&& it_offset_name = std::search(
|
||||
std::begin(section_names),
|
||||
std::end(section_names),
|
||||
section->name().c_str(),
|
||||
section->name().c_str() + section->name().size() + 1);
|
||||
|
||||
if (it_offset_name == std::end(section_names)) {
|
||||
throw LIEF::not_found(""); // TODO: msg
|
||||
}
|
||||
|
||||
|
||||
// Write Section's content
|
||||
if (section->size() > 0) {
|
||||
this->ios_.seekp(section->file_offset());
|
||||
@ -278,7 +316,7 @@ void Builder::build_sections(void) {
|
||||
section->name().c_str() + section->name().size() + 1);
|
||||
|
||||
if (it_offset_name == std::end(section_names)) {
|
||||
throw LIEF::not_found(""); // TODO: msg
|
||||
throw LIEF::not_found("Section name not found");
|
||||
}
|
||||
|
||||
const Elf_Off offset_name = static_cast<Elf_Off>(std::distance(std::begin(section_names), it_offset_name));
|
||||
@ -438,6 +476,7 @@ void Builder::build_static_symbols(void) {
|
||||
content.write_conv<Elf_Sym>(sym_hdr);
|
||||
}
|
||||
|
||||
// FIXME: Handle increase of size in symbol_str_section
|
||||
symbol_str_section.content(std::move(string_table));
|
||||
symbol_section.content(std::move(content.raw()));
|
||||
|
||||
@ -1098,6 +1137,131 @@ void Builder::build_dynamic_symbols(void) {
|
||||
|
||||
}
|
||||
|
||||
template<typename ELF_T>
|
||||
void Builder::build_section_relocations(void) {
|
||||
using Elf_Addr = typename ELF_T::Elf_Addr;
|
||||
using Elf_Xword = typename ELF_T::Elf_Xword;
|
||||
using Elf_Sxword = typename ELF_T::Elf_Sxword;
|
||||
|
||||
using Elf_Rela = typename ELF_T::Elf_Rela;
|
||||
using Elf_Rel = typename ELF_T::Elf_Rel;
|
||||
VLOG(VDEBUG) << "[+] Building object relocations";
|
||||
|
||||
it_object_relocations object_relocations = this->binary_->object_relocations();
|
||||
|
||||
bool isRela = object_relocations[0].is_rela();
|
||||
if (not std::all_of(
|
||||
std::begin(object_relocations),
|
||||
std::end(object_relocations),
|
||||
[isRela] (const Relocation& relocation) {
|
||||
return relocation.is_rela() == isRela;
|
||||
})) {
|
||||
throw LIEF::type_error("Object relocations are not of the same type");
|
||||
}
|
||||
|
||||
it_sections sections = this->binary_->sections();
|
||||
|
||||
std::vector<Section*> rel_section;
|
||||
for(Section& S: sections)
|
||||
if(S.type() == ((isRela)?ELF_SECTION_TYPES::SHT_RELA:ELF_SECTION_TYPES::SHT_REL))
|
||||
rel_section.push_back(&S);
|
||||
|
||||
|
||||
// FIXME: Warn if not rel section found?
|
||||
|
||||
for(Section* section: rel_section) {
|
||||
|
||||
if (section->information() == 0 or section->information() >= sections.size())
|
||||
throw LIEF::not_found("Unable to find associated section for SHT_REL{A} section");
|
||||
|
||||
const size_t sh_info = section->information();
|
||||
|
||||
Section& AssociatedSection = sections[sh_info];
|
||||
|
||||
std::vector<uint8_t> content;
|
||||
for (const Relocation& relocation : this->binary_->object_relocations()) {
|
||||
|
||||
// Only write relocation in the matching section
|
||||
// (relocation for .text in .rela.text)
|
||||
// FIXME: static relocation on a new section will be ignored (SILENTLY!!)
|
||||
if(relocation.section_ != &AssociatedSection)
|
||||
continue;
|
||||
|
||||
uint32_t idx = 0;
|
||||
if (relocation.has_symbol()) {
|
||||
const Symbol& symbol = relocation.symbol();
|
||||
auto it_name = std::find_if(
|
||||
std::begin(this->binary_->dynamic_symbols_),
|
||||
std::end(this->binary_->dynamic_symbols_),
|
||||
[&symbol] (const Symbol* s) {
|
||||
return s == &symbol;
|
||||
});
|
||||
|
||||
if (it_name == std::end(this->binary_->dynamic_symbols_)) {
|
||||
// FIXME: Do we have a way to walk both?
|
||||
auto it_name = std::find_if(
|
||||
std::begin(this->binary_->static_symbols_),
|
||||
std::end(this->binary_->static_symbols_),
|
||||
[&symbol] (const Symbol* s) {
|
||||
return s == &symbol;
|
||||
});
|
||||
|
||||
if (it_name == std::end(this->binary_->static_symbols_)) {
|
||||
throw not_found("Unable to find the symbol associated with the relocation");
|
||||
}
|
||||
idx = static_cast<uint32_t>(std::distance(std::begin(this->binary_->static_symbols_), it_name));
|
||||
} else
|
||||
idx = static_cast<uint32_t>(std::distance(std::begin(this->binary_->dynamic_symbols_), it_name));
|
||||
}
|
||||
|
||||
|
||||
Elf_Xword info = 0;
|
||||
if (std::is_same<ELF_T, ELF32>::value) {
|
||||
info = (static_cast<Elf_Xword>(idx) << 8) | relocation.type();
|
||||
} else {
|
||||
info = (static_cast<Elf_Xword>(idx) << 32) | (relocation.type() & 0xffffffffL);
|
||||
}
|
||||
|
||||
if (isRela) {
|
||||
Elf_Rela relahdr;
|
||||
relahdr.r_offset = static_cast<Elf_Addr>(relocation.address());
|
||||
relahdr.r_info = static_cast<Elf_Xword>(info);
|
||||
relahdr.r_addend = static_cast<Elf_Sxword>(relocation.addend());
|
||||
|
||||
content.insert(
|
||||
std::end(content),
|
||||
reinterpret_cast<uint8_t*>(&relahdr),
|
||||
reinterpret_cast<uint8_t*>(&relahdr) + sizeof(Elf_Rela));
|
||||
|
||||
} else {
|
||||
Elf_Rel relhdr;
|
||||
relhdr.r_offset = static_cast<Elf_Addr>(relocation.address());
|
||||
relhdr.r_info = static_cast<Elf_Xword>(info);
|
||||
|
||||
content.insert(
|
||||
std::end(content),
|
||||
reinterpret_cast<uint8_t*>(&relhdr),
|
||||
reinterpret_cast<uint8_t*>(&relhdr) + sizeof(Elf_Rel));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
VLOG(VDEBUG) << "Section associated with object relocations: " << section->name();
|
||||
VLOG(VDEBUG) << "Is Rela: " << std::boolalpha << isRela;
|
||||
// Relocation the '.rela.xxxx' section
|
||||
if (content.size() > section->original_size()) {
|
||||
Section rela_section(section->name(), (isRela)?ELF_SECTION_TYPES::SHT_RELA:ELF_SECTION_TYPES::SHT_REL);
|
||||
rela_section.content(content);
|
||||
this->binary_->add(rela_section, false);
|
||||
this->binary_->remove(*section, true);
|
||||
|
||||
return this->build<ELF_T>();
|
||||
|
||||
}
|
||||
section->content(std::move(content));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ELF_T>
|
||||
void Builder::build_dynamic_relocations(void) {
|
||||
using Elf_Addr = typename ELF_T::Elf_Addr;
|
||||
|
@ -339,8 +339,21 @@ void Parser::parse_binary(void) {
|
||||
nb_entries,
|
||||
this->binary_->sections_[section->link()]);
|
||||
}
|
||||
|
||||
it_symtab_section = std::find_if(
|
||||
it_symtab_section + 1,
|
||||
std::end(this->binary_->sections_),
|
||||
[] (const Section* section)
|
||||
{
|
||||
return section != nullptr and section->type() == ELF_SECTION_TYPES::SHT_SYMTAB;
|
||||
});
|
||||
|
||||
if (it_symtab_section != std::end(this->binary_->sections_)) {
|
||||
LOG(WARNING) << "Support for multiple SHT_SYMTAB section is not implemented\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse Symbols's hash
|
||||
// ====================
|
||||
|
||||
@ -418,21 +431,14 @@ void Parser::parse_binary(void) {
|
||||
// Try to parse using sections
|
||||
if (this->binary_->relocations_.size() == 0) {
|
||||
for (const Section& section : this->binary_->sections()) {
|
||||
Section* section_associated = nullptr;
|
||||
if (section.information() > 0 and section.information() < this->binary_->sections_.size()) {
|
||||
const size_t sh_info = section.information();
|
||||
section_associated = this->binary_->sections_[sh_info];
|
||||
}
|
||||
|
||||
try {
|
||||
if (section.type() == ELF_SECTION_TYPES::SHT_REL) {
|
||||
|
||||
this->parse_section_relocations<ELF_T, typename ELF_T::Elf_Rel>(
|
||||
section.file_offset(), section.size(), section_associated);
|
||||
this->parse_section_relocations<ELF_T, typename ELF_T::Elf_Rel>(section);
|
||||
}
|
||||
else if (section.type() == ELF_SECTION_TYPES::SHT_RELA) {
|
||||
this->parse_section_relocations<ELF_T, typename ELF_T::Elf_Rela>(
|
||||
section.file_offset(), section.size(), section_associated);
|
||||
this->parse_section_relocations<ELF_T, typename ELF_T::Elf_Rela>(section);
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
@ -1326,17 +1332,34 @@ void Parser::parse_pltgot_relocations(uint64_t offset, uint64_t size) {
|
||||
}
|
||||
|
||||
template<typename ELF_T, typename REL_T>
|
||||
void Parser::parse_section_relocations(uint64_t offset, uint64_t size, Section *applies_to) {
|
||||
void Parser::parse_section_relocations(Section const& section) {
|
||||
using Elf_Rel = typename ELF_T::Elf_Rel;
|
||||
using Elf_Rela = typename ELF_T::Elf_Rela;
|
||||
|
||||
static_assert(std::is_same<REL_T, Elf_Rel>::value or
|
||||
std::is_same<REL_T, Elf_Rela>::value, "REL_T must be Elf_Rel or Elf_Rela");
|
||||
|
||||
const uint64_t offset_relocations = offset;
|
||||
// A relocation section can reference two other sections: a symbol table,
|
||||
// identified by the sh_info section header entry, and a section to modify,
|
||||
// identified by the sh_link
|
||||
// BUT: in practice sh_info and sh_link are inverted
|
||||
Section* applies_to = nullptr;
|
||||
if (section.information() > 0 and section.information() < this->binary_->sections_.size()) {
|
||||
const size_t sh_info = section.information();
|
||||
applies_to = this->binary_->sections_[sh_info];
|
||||
}
|
||||
|
||||
// FIXME: Use it
|
||||
// Section* section_associated = nullptr;
|
||||
// if (section.link() > 0 and section.link() < this->binary_->sections_.size()) {
|
||||
// const size_t sh_link = section.link();
|
||||
// section_associated = this->binary_->sections_[sh_link];
|
||||
// }
|
||||
|
||||
const uint64_t offset_relocations = section.file_offset();
|
||||
const uint8_t shift = std::is_same<ELF_T, ELF32>::value ? 8 : 32;
|
||||
|
||||
uint32_t nb_entries = static_cast<uint32_t>(size / sizeof(REL_T));
|
||||
uint32_t nb_entries = static_cast<uint32_t>(section.size() / sizeof(REL_T));
|
||||
nb_entries = std::min<uint32_t>(nb_entries, Parser::NB_MAX_RELOCATIONS);
|
||||
|
||||
this->stream_->setpos(offset_relocations);
|
||||
@ -1366,9 +1389,12 @@ void Parser::parse_section_relocations(uint64_t offset, uint64_t size, Section *
|
||||
std::begin(this->binary_->relocations_),
|
||||
std::end(this->binary_->relocations_),
|
||||
[&reloc] (const Relocation* r) {
|
||||
return r->address() == reloc->address() and
|
||||
r->type() == reloc->type() and
|
||||
r->addend() == reloc->addend();
|
||||
bool is_same = r->address() == reloc->address() and
|
||||
r->type() == reloc->type() and
|
||||
r->addend() == reloc->addend();
|
||||
if(r->has_symbol())
|
||||
is_same &= reloc->has_symbol() and reloc->symbol() == r->symbol();
|
||||
return is_same;
|
||||
}) == std::end(this->binary_->relocations_)) {
|
||||
this->binary_->relocations_.push_back(reloc.release());
|
||||
}
|
||||
|
@ -205,6 +205,9 @@ if (PYTHON_TESTS_ENABLED)
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/test_dynamic.py")
|
||||
|
||||
ADD_PYTHON_TEST(ELF_PYTHON_test_static
|
||||
${PYTHON_EXECUTABLE}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/test_static.py")
|
||||
|
||||
ADD_PYTHON_TEST(ELF_PYTHON_hash_test
|
||||
${PYTHON_EXECUTABLE}
|
||||
|
162
tests/elf/test_static.py
Normal file
162
tests/elf/test_static.py
Normal file
@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python
|
||||
import unittest
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import lief
|
||||
from lief import Logger
|
||||
Logger.set_level(lief.LOGGING_LEVEL.INFO)
|
||||
|
||||
from subprocess import Popen
|
||||
from unittest import TestCase
|
||||
|
||||
BINADD_C = """\
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int add(int a, int b);
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
printf("Usage: %s <a> <b>\\n", argv[0]);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
int res = add(atoi(argv[1]), atoi(argv[2]));
|
||||
printf("From myLIb, a + b = %d\\n", res);
|
||||
return 0;
|
||||
}
|
||||
"""
|
||||
|
||||
ADD_C = """\
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
"""
|
||||
|
||||
class LibAddSample(object):
|
||||
COUNT = 0
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.tmp_dir = tempfile.mkdtemp(suffix='_lief_sample_{:d}'.format(LibAddSample.COUNT))
|
||||
self.logger.debug("temp dir: {}".format(self.tmp_dir))
|
||||
|
||||
LibAddSample.COUNT += 1
|
||||
|
||||
self.binadd_path = os.path.join(self.tmp_dir, "binadd.c")
|
||||
self.add_c_path = os.path.join(self.tmp_dir, "add.c")
|
||||
self.binadd_obj = os.path.join(self.tmp_dir, "binadd.o")
|
||||
self.binadd_bin = os.path.join(self.tmp_dir, "binadd.exe")
|
||||
|
||||
self.compiler = '/usr/bin/cc'
|
||||
|
||||
if self.compiler is None:
|
||||
self.logger.error("Unable to find a compiler")
|
||||
sys.exit(0)
|
||||
|
||||
self.logger.debug("Compiler: {}".format(self.compiler))
|
||||
|
||||
with open(self.binadd_path, 'w') as f:
|
||||
f.write(BINADD_C)
|
||||
|
||||
with open(self.add_c_path, 'w') as f:
|
||||
f.write(ADD_C)
|
||||
|
||||
self._compile_objadd()
|
||||
|
||||
|
||||
def _compile_objadd(self):
|
||||
if os.path.isfile(self.binadd_obj):
|
||||
os.remove(self.binadd_obj)
|
||||
|
||||
cmd = [self.compiler, '-c', '-o', self.binadd_obj, self.binadd_path]
|
||||
self.logger.debug("Compile 'binadd' with: {}".format(" ".join(cmd)))
|
||||
p = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
stdout, _ = p.communicate()
|
||||
self.logger.debug(stdout)
|
||||
|
||||
def compile_object_to_bin(self):
|
||||
if os.path.isfile(self.binadd_bin):
|
||||
os.remove(self.binadd_bin)
|
||||
|
||||
cmd = [self.compiler, '-o', self.binadd_bin, self.binadd_obj, self.add_c_path]
|
||||
self.logger.debug("Compile 'binadd' with: {}".format(" ".join(cmd)))
|
||||
p = Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
stdout, _ = p.communicate()
|
||||
self.logger.debug(stdout)
|
||||
|
||||
@property
|
||||
def binadd(self):
|
||||
return self.binadd_bin
|
||||
|
||||
@property
|
||||
def objadd(self):
|
||||
return self.binadd_obj
|
||||
|
||||
@property
|
||||
def directory(self):
|
||||
return self.tmp_dir
|
||||
|
||||
def remove(self):
|
||||
if os.path.isdir(self.directory):
|
||||
shutil.rmtree(self.directory)
|
||||
|
||||
def __del__(self):
|
||||
self.remove()
|
||||
|
||||
|
||||
class TestStatic(TestCase):
|
||||
def setUp(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
||||
def test_write_object(self):
|
||||
sample = LibAddSample()
|
||||
tmp_file = os.path.join(sample.directory, "newfile.o")
|
||||
binadd = lief.parse(sample.objadd)
|
||||
init_obj = [str(o).strip() for o in binadd.object_relocations]
|
||||
|
||||
binadd.write(tmp_file)
|
||||
binadd = lief.parse(tmp_file)
|
||||
new_obj = [str(o).strip() for o in binadd.object_relocations]
|
||||
|
||||
self.assertEqual(len(init_obj), len(new_obj))
|
||||
|
||||
for new, old in zip(new_obj, init_obj):
|
||||
self.assertEqual(new, old)
|
||||
|
||||
# Check it can still be compiled
|
||||
sample.compile_object_to_bin()
|
||||
self.assertEqual(subprocess.check_output([sample.binadd_bin, "2", "3"]).decode('ascii', 'ignore'),
|
||||
'From myLIb, a + b = 5\n')
|
||||
|
||||
@unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux")
|
||||
def test_update_addend_object(self):
|
||||
sample = LibAddSample()
|
||||
tmp_file = os.path.join(sample.directory, "newfile.o")
|
||||
binadd = lief.parse(sample.objadd)
|
||||
reloc = next(o for o in binadd.object_relocations if o.symbol.name == "add")
|
||||
|
||||
reloc.addend = 0xABCD
|
||||
binadd.write(tmp_file)
|
||||
binadd = lief.parse(tmp_file)
|
||||
reloc = next(o for o in binadd.object_relocations if o.symbol.name == "add")
|
||||
|
||||
self.assertEqual(reloc.addend, 0xABCD)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(logging.DEBUG)
|
||||
root_logger.addHandler(ch)
|
||||
|
||||
unittest.main(verbosity=2)
|
||||
|
Loading…
x
Reference in New Issue
Block a user