fix(relinker): allow identical Windows function export aliases
This commit is contained in:
parent
a11c8f7f70
commit
5ea9ce2e40
|
|
@ -81,6 +81,20 @@ target_include_directories(relinker PRIVATE
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/../libs
|
||||
)
|
||||
|
||||
if(BUILD_TESTING AND WIN32)
|
||||
get_target_property(windowsAliasTestSources relinker SOURCES)
|
||||
list(REMOVE_ITEM windowsAliasTestSources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp)
|
||||
add_test_executable(windows_guest_export_alias_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/WindowsExportAliasTests.cpp
|
||||
${windowsAliasTestSources})
|
||||
get_target_property(windowsAliasIncludes relinker INCLUDE_DIRECTORIES)
|
||||
target_include_directories(windows_guest_export_alias_tests PRIVATE ${windowsAliasIncludes})
|
||||
add_test(NAME windows_guest_export_aliases COMMAND windows_guest_export_alias_tests)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_compile_options(relinker PRIVATE -municode)
|
||||
target_link_options(relinker PRIVATE -municode)
|
||||
|
|
|
|||
|
|
@ -120,9 +120,16 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
|
|||
return false;
|
||||
};
|
||||
std::map<std::string, std::uint32_t> exports;
|
||||
std::map<std::string, const Relinker::GuestSymbol*> exportSymbols;
|
||||
PeSection tlsExports{".tlsrefs", nextRva, SectionRead | 0x40u, {}};
|
||||
for (const auto& symbol : guest.Symbols) {
|
||||
if (symbol.Section == 0 || symbol.Section == Relinker::AbsoluteSection || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
|
||||
if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue;
|
||||
const auto [previous, inserted] = exportSymbols.emplace(symbol.Name, &symbol);
|
||||
if (!inserted) {
|
||||
if (!Relinker::IdenticalOrdinaryFunctionExport(*previous->second, symbol)) throw Domain::RelinkerException("Duplicate guest export: " + symbol.Name);
|
||||
continue;
|
||||
}
|
||||
if (symbol.Section == Relinker::AbsoluteSection) continue;
|
||||
if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported guest export section: " + symbol.Name);
|
||||
std::uint32_t rva = 0;
|
||||
if ((symbol.Info & 15) == 6) {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,16 @@ struct GuestSymbol {
|
|||
std::uint64_t Value;
|
||||
std::uint64_t Size;
|
||||
std::string Library;
|
||||
std::string QualifiedName;
|
||||
};
|
||||
|
||||
inline bool IdenticalOrdinaryFunctionExport(const GuestSymbol& left, const GuestSymbol& right) {
|
||||
return left.Section != 0 && left.Section < 0xff00 && (left.Info >> 4) != 0 &&
|
||||
left.Visibility != 1 && left.Visibility != 2 && (left.Info & 15) == 2 &&
|
||||
left.Info == right.Info && left.Visibility == right.Visibility &&
|
||||
left.Section == right.Section && left.Value == right.Value && left.Size == right.Size;
|
||||
}
|
||||
|
||||
struct GuestImage {
|
||||
std::filesystem::path SourcePath;
|
||||
std::string OutputName;
|
||||
|
|
@ -49,7 +57,7 @@ struct GuestImage {
|
|||
class GuestImageReader {
|
||||
public:
|
||||
std::vector<std::string> ReadModuleNames(const std::vector<std::uint8_t>& bytes) const;
|
||||
GuestImage Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const;
|
||||
GuestImage Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes, bool windows = false) const;
|
||||
};
|
||||
|
||||
struct GuestArtifact {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ std::vector<std::string> GuestImageReader::ReadModuleNames(const std::vector<std
|
|||
return names;
|
||||
}
|
||||
|
||||
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes) const {
|
||||
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes, const bool windows) const {
|
||||
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); };
|
||||
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) {
|
||||
|
|
@ -204,19 +204,21 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
if (!importModules.emplace(value >> 48, string(value & 0xffffffffu)).second) fail("Duplicate import module ID");
|
||||
std::vector<std::string> neededLibraries;
|
||||
for (const auto offset : needed) neededLibraries.push_back(string(offset));
|
||||
std::set<std::string> exports;
|
||||
std::map<std::string, std::size_t> exports;
|
||||
for (std::uint64_t offset = 0; offset < symSize; offset += 24) {
|
||||
auto name = string(Io::ReadU32(bytes, symOffset + offset));
|
||||
const auto library = Io::ReadU16(bytes, symOffset + offset + 6) == 0 ? Domain::ImportModule(name, importModules, neededLibraries) : std::string{};
|
||||
if (Io::ReadU16(bytes, symOffset + offset + 6) == 0 && library.empty()) {
|
||||
if (auto declared = Domain::DeclaredImportModule(name, importModules); !declared.empty()) image.Dynamic.UnmatchedImportModules.insert(std::move(declared));
|
||||
}
|
||||
const auto qualifiedName = name;
|
||||
name = name.substr(0, name.find('#'));
|
||||
const auto info = bytes[symOffset + offset + 4];
|
||||
const auto visibility = bytes[symOffset + offset + 5];
|
||||
const auto section = Io::ReadU16(bytes, symOffset + offset + 6);
|
||||
const auto value = Io::ReadU64(bytes, symOffset + offset + 8);
|
||||
const auto size = Io::ReadU64(bytes, symOffset + offset + 16);
|
||||
const GuestSymbol symbol{name, info, visibility, section, value, size, library, qualifiedName};
|
||||
if (offset == 0 && (info != 0 || visibility != 0 || section != 0 || value != 0 || size != 0 || !name.empty())) fail("Invalid null symbol");
|
||||
if (offset == 0) {
|
||||
image.Symbols.push_back({{}, 0, 0, 0, 0, 0});
|
||||
|
|
@ -224,7 +226,8 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
}
|
||||
if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name);
|
||||
if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) {
|
||||
if (name.empty() || !exports.insert(name).second) fail("Duplicate or empty export after stripping #: " + name);
|
||||
const auto [found, inserted] = exports.emplace(name, image.Symbols.size());
|
||||
if (name.empty() || (!inserted && (!windows || !IdenticalOrdinaryFunctionExport(image.Symbols[found->second], symbol)))) fail("Duplicate or empty export after stripping #: " + name);
|
||||
}
|
||||
if ((info & 15) == 6 && section != 0 && (tls == nullptr || value > tls->MemorySize || size > tls->MemorySize - value)) fail("TLS symbol exceeds TLS segment: " + name);
|
||||
if (section != 0 && section != AbsoluteSection && (info & 15) != 6) {
|
||||
|
|
@ -234,7 +237,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
if (image.Dynamic.DynStrData.size() > std::numeric_limits<std::uint32_t>::max()) fail("String table too large");
|
||||
Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast<std::uint32_t>(image.Dynamic.DynStrData.size()));
|
||||
Io::AppendString(image.Dynamic.DynStrData, name);
|
||||
image.Symbols.push_back({name, info, visibility, section, value, size, library});
|
||||
image.Symbols.push_back(symbol);
|
||||
}
|
||||
if (tags.contains(14)) {
|
||||
image.Soname = string(tags.at(14));
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
return name == identity || name == identity + ".prx" || name == identity + ".sprx" || name == identity + ".suprx";
|
||||
});
|
||||
};
|
||||
for (const auto& path : paths) discovered.emplace(path, GuestImageReader().Read(path, reader.Read(Io::Utf8Path(path))));
|
||||
for (const auto& path : paths) discovered.emplace(path, GuestImageReader().Read(path, reader.Read(Io::Utf8Path(path)), windows));
|
||||
if (!unmatchedExclusions.empty()) throw Domain::RelinkerException("Excluded guest module file not found: " + *unmatchedExclusions.begin());
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <elfpatcher/windows/WindowsLoadImage.hpp>
|
||||
#include <elfpatcher/windows/WindowsPeFormat.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <domain/ImportModule.hpp>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
static void Check(bool ok, const char* message) { if (!ok) throw std::runtime_error(message); }
|
||||
static void Ph(Bytes& b, int i, unsigned type, unsigned flags, unsigned off, unsigned va, unsigned size) {
|
||||
const auto p=64+i*56; Io::WriteU32(b,p,type); Io::WriteU32(b,p+4,flags);
|
||||
Io::WriteU64(b,p+8,off);Io::WriteU64(b,p+16,va);Io::WriteU64(b,p+24,va);
|
||||
Io::WriteU64(b,p+32,size);Io::WriteU64(b,p+40,size);Io::WriteU64(b,p+48,8);
|
||||
}
|
||||
static Bytes Fixture(bool reverse=false) {
|
||||
Bytes b(0x4000);std::memcpy(b.data(),"\x7f" "ELF\x02\x01\x01",7);
|
||||
Io::WriteU16(b,16,3);Io::WriteU16(b,18,62);Io::WriteU32(b,20,1);
|
||||
Io::WriteU64(b,32,64);Io::WriteU16(b,52,64);Io::WriteU16(b,54,56);Io::WriteU16(b,56,3);
|
||||
Ph(b,0,1,5,0x1000,0,0x1000);Ph(b,1,1,6,0x2000,0x2000,0x1000);
|
||||
b[0x1020]=0xc3;b[0x1028]=0xc3;
|
||||
std::string strings(1,'\0');auto str=[&](const char* s){auto o=strings.size();strings+=s;strings+='\0';return o;};
|
||||
const auto a=str(reverse?"alias#B#A":"alias#A#A"),c=str(reverse?"alias#A#A":"alias#B#A");
|
||||
const auto imp=str("imported#A#B"),host=str("fixtureHost.prx"),module=str("fixtureModule");
|
||||
std::copy(strings.begin(),strings.end(),b.begin()+0x2200);
|
||||
const std::pair<std::uint64_t,std::uint64_t> tags[]={{5,0x2200},{10,strings.size()},{6,0x2400},{11,24},{4,0x2600},{7,0x2700},{8,48},{9,24},{1,host},{0x61000043,module},{0x61000045,(1ull<<48)|host},{0,0}};
|
||||
Ph(b,2,2,6,0x2000,0x2000,sizeof(tags));
|
||||
for(unsigned i=0;i<std::size(tags);++i){Io::WriteU64(b,0x2000+i*16,tags[i].first);Io::WriteU64(b,0x2008+i*16,tags[i].second);}
|
||||
Io::WriteU32(b,0x2600,1);Io::WriteU32(b,0x2604,4);
|
||||
for(unsigned i=1;i<=3;++i){const auto o=0x2400+i*24;Io::WriteU32(b,o,i==1?a:i==2?c:imp);b[o+4]=0x12;b[o+5]=i==3?0:3;Io::WriteU16(b,o+6,i==3?0:1);Io::WriteU64(b,o+8,i==3?0:0x20);Io::WriteU64(b,o+16,i==3?0:1);}
|
||||
for(unsigned i=0;i<2;++i){Io::WriteU64(b,0x2700+i*24,0x2800+i*8);Io::WriteU64(b,0x2708+i*24,((i+1ull)<<32)|1);}
|
||||
return b;
|
||||
}
|
||||
static std::size_t Offset(const Bytes& pe,std::uint32_t rva) {
|
||||
const auto nt=Io::ReadU32(pe,0x3c);const auto count=Io::ReadU16(pe,nt+6);const auto sections=nt+24+Io::ReadU16(pe,nt+20);
|
||||
for(unsigned i=0;i<count;++i){auto p=sections+i*40;auto start=Io::ReadU32(pe,p+12),size=Io::ReadU32(pe,p+16);if(rva>=start&&rva-start<size)return Io::ReadU32(pe,p+20)+rva-start;}
|
||||
throw std::runtime_error("unmapped PE fixture RVA");
|
||||
}
|
||||
template<class F> static void Reject(F&& f,const char* expected) {
|
||||
bool rejected=false;try{f();}catch(const Domain::RelinkerException& e){rejected=std::string(e.what()).find(expected)!=std::string::npos;}
|
||||
Check(rejected,expected);
|
||||
}
|
||||
static void Positive(bool reverse) {
|
||||
const auto bytes=Fixture(reverse);const auto g=Relinker::GuestImageReader().Read("generic.prx",bytes,true);
|
||||
Check(g.Symbols.size()==4&&g.Bytes==bytes,"input symbols/bytes changed");
|
||||
Check(g.Symbols[1].Name=="alias"&&g.Symbols[2].Name=="alias","stripped lookup names");
|
||||
Check(g.Symbols[1].QualifiedName==(reverse?"alias#B#A":"alias#A#A")&&g.Symbols[2].QualifiedName==(reverse?"alias#A#A":"alias#B#A"),"qualified names lost");
|
||||
Check(g.Symbols[1].Info==18&&g.Symbols[2].Info==18&&g.Symbols[1].Visibility==3&&g.Symbols[2].Visibility==3&&g.Symbols[1].Section==1&&g.Symbols[2].Section==1&&g.Symbols[1].Value==0x20&&g.Symbols[2].Value==0x20&&g.Symbols[1].Size==1&&g.Symbols[2].Size==1,"raw tuple changed");
|
||||
Check((Io::ReadU64(g.Dynamic.RelaData,8)>>32)==1&&(Io::ReadU64(g.Dynamic.RelaData,32)>>32)==2,"alias relocation indices changed");
|
||||
Check(g.Symbols[3].QualifiedName=="imported#A#B"&&g.Symbols[3].Library=="fixtureHost.prx","qualified import changed");
|
||||
Domain::GuestRuntime runtime;const auto pe=Elfpatcher::GuestModuleWriter().WriteWindows(g,runtime);
|
||||
const auto directory=Offset(pe,Io::ReadU32(pe,Io::ReadU32(pe,0x3c)+24+112));
|
||||
Check(Io::ReadU32(pe,directory+20)==1&&Io::ReadU32(pe,directory+24)==1,"multiple PE exports");
|
||||
const auto function=Io::ReadU32(pe,Offset(pe,Io::ReadU32(pe,directory+28)));
|
||||
const auto nameRva=Io::ReadU32(pe,Offset(pe,Io::ReadU32(pe,directory+32)));
|
||||
Check(std::strcmp(reinterpret_cast<const char*>(pe.data()+Offset(pe,nameRva)),"alias")==0,"wrong export name");
|
||||
Elfpatcher::Windows::WindowsLoadImage image(g.Bytes,g.Headers,false);
|
||||
Check(function==image.GetRva(0x20,1),"wrong export RVA");
|
||||
for(auto target:{0x2800,0x2808})Check(Io::ReadU64(pe,Offset(pe,image.GetRva(target,8)))==Elfpatcher::Windows::ImageBase+function,"wrong alias relocation value");
|
||||
}
|
||||
int main() {
|
||||
try {
|
||||
Positive(false);Positive(true);
|
||||
Reject([]{Relinker::GuestImageReader().Read("generic.prx",Fixture());},"Duplicate or empty export");
|
||||
const std::array<std::pair<unsigned,std::uint64_t>,7> changes={{{4,0x22},{4,0x11},{5,0},{6,2},{8,0x28},{16,2},{6,Relinker::AbsoluteSection}}};
|
||||
for(const auto& [field,value]:changes){auto b=Fixture();auto off=0x2400+48+field;if(field<6)b[off]=value;else if(field==6)Io::WriteU16(b,off,value);else Io::WriteU64(b,off,value);Reject([&]{Relinker::GuestImageReader().Read("generic.prx",b,true);},"Duplicate or empty export");}
|
||||
auto good=Relinker::GuestImageReader().Read("generic.prx",Fixture(),true);
|
||||
for(unsigned kind=0;kind<10;++kind){auto g=good;auto& s=g.Symbols[2];switch(kind){case 0:s.Value+=8;break;case 1:s.Size+=1;break;case 2:s.Info=0x22;break;case 3:s.Info=0x11;break;case 4:s.Visibility=0;break;case 5:s.Section=2;break;case 6:g.Symbols[1].Info=s.Info=0x11;break;case 7:g.Symbols[1].Section=s.Section=Relinker::AbsoluteSection;break;case 8:g.Symbols[1].Info=s.Info=0x16;g.Dynamic.RelaData.clear();break;case 9:g.Symbols[1].Info=s.Info=0x10;break;}Check(!Relinker::IdenticalOrdinaryFunctionExport(g.Symbols[1],s),"nonordinary/conflicting tuple accepted");Domain::GuestRuntime r;Reject([&]{Elfpatcher::GuestModuleWriter().WriteWindows(g,r);},kind==8?"TLS":"Duplicate guest export");}
|
||||
Check(Domain::DeclaredImportModule("same#A#B",{{1,"one"},{2,"two"}})=="one"&&Domain::DeclaredImportModule("same#A#C",{{1,"one"},{2,"two"}})=="two","module-qualified IDs merged");
|
||||
std::cout<<"WINDOWS_EXPORT_ALIAS_CASES_PASSED\npreserved-indices reversed-order conflicting-fields strict-nonwindows qualified-import writer-guard\n";
|
||||
} catch(const std::exception& e){std::cerr<<e.what()<<'\n';return 1;}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
"""Generic converter regression; emitted guest PE is inspected, never executed."""
|
||||
from pathlib import Path
|
||||
import hashlib,json,struct,subprocess,sys
|
||||
sys.path.insert(0,str(Path(__file__).resolve().parent))
|
||||
from test_windows_import_modules import executable
|
||||
|
||||
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
|
||||
def fixture():
|
||||
b=bytearray(0x4000);b[:7]=b'\x7fELF\x02\x01\x01'
|
||||
struct.pack_into('<HHI',b,16,3,62,1);struct.pack_into('<Q',b,32,64);struct.pack_into('<HHH',b,52,64,56,3)
|
||||
def ph(i,t,fl,off,va,size):struct.pack_into('<IIQQQQQQ',b,64+i*56,t,fl,off,va,va,size,size,8)
|
||||
ph(0,1,5,0x1000,0,0x1000);ph(1,1,6,0x2000,0x2000,0x1000)
|
||||
b[0x1020:0x1026]=b'\xb8\x07\0\0\0\xc3'
|
||||
strings=b'\0alias#A#A\0alias#B#A\0fixtureModule\0';b[0x2200:0x2200+len(strings)]=strings
|
||||
tags=[(5,0x2200),(10,len(strings)),(6,0x2400),(11,24),(4,0x2600),(7,0x2700),(8,48),(9,24),(0x61000043,21),(0,0)]
|
||||
ph(2,2,6,0x2000,0x2000,len(tags)*16)
|
||||
for i,(t,v) in enumerate(tags):struct.pack_into('<QQ',b,0x2000+i*16,t,v)
|
||||
struct.pack_into('<II',b,0x2600,1,3)
|
||||
for i,no in [(1,1),(2,11)]:struct.pack_into('<IBBHQQ',b,0x2400+i*24,no,0x12,3,1,0x20,6)
|
||||
for i in range(2):struct.pack_into('<QQq',b,0x2700+i*24,0x2800+i*8,((i+1)<<32)|1,0)
|
||||
return b
|
||||
def main():
|
||||
stock,current,out=map(Path,sys.argv[1:]);assert not out.exists();out.mkdir()
|
||||
case=out/'input';(case/'sce_module').mkdir(parents=True)
|
||||
module=case/'sce_module/alias.prx';module.write_bytes(fixture());source=case/'input.elf';source.write_bytes(executable('alias.prx','alias#A#B','fixtureModule'))
|
||||
pins={str(p):sha(p) for p in (module,source)};rows={}
|
||||
for label,converter in [('stock',stock),('corrected',current)]:
|
||||
target=out/label;target.mkdir();output=target/'app.exe'
|
||||
args=[str(converter),'--windows','--sce-module-path',str(case),str(source),str(output)]
|
||||
r=subprocess.run(args,cwd=target,stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE,timeout=30)
|
||||
rows[label]=dict(exitCode=r.returncode,stdout=r.stdout.decode(errors='replace'),stderr=r.stderr.decode(errors='replace'),converterSha256=sha(converter),outputExists=output.exists())
|
||||
(out/(label+'-stdout.txt')).write_bytes(r.stdout);(out/(label+'-stderr.txt')).write_bytes(r.stderr)
|
||||
if label=='stock':assert r.returncode==2 and 'Duplicate or empty export after stripping #: alias' in rows[label]['stderr'] and not output.exists()
|
||||
else:assert r.returncode==0 and output.is_file(),rows[label]
|
||||
guest=out/'corrected/app0/sce_module/alias.prx.guest.prx';pe=guest.read_bytes();nt=struct.unpack_from('<I',pe,0x3c)[0];opt=nt+24;sections=opt+struct.unpack_from('<H',pe,nt+20)[0]
|
||||
def offset(rva):
|
||||
for i in range(struct.unpack_from('<H',pe,nt+6)[0]):
|
||||
_,va,sz,raw=struct.unpack_from('<IIII',pe,sections+i*40+8)
|
||||
if va<=rva<va+sz:return raw+rva-va
|
||||
raise AssertionError('unmapped PE RVA')
|
||||
ed=offset(struct.unpack_from('<I',pe,opt+112)[0]);functions,names=struct.unpack_from('<II',pe,ed+20);assert functions==names==1
|
||||
fn,ns,ords=struct.unpack_from('<III',pe,ed+28);rva=struct.unpack_from('<I',pe,offset(fn))[0];no=offset(struct.unpack_from('<I',pe,offset(ns))[0]);name=pe[no:pe.index(b'\0',no)].decode();assert name=='alias'
|
||||
assert rva==0x10020 and all(struct.unpack_from('<Q',pe,offset(0x12800+i*8))[0]==0x140000000+rva for i in range(2))
|
||||
assert all(sha(Path(p))==h for p,h in pins.items())
|
||||
result=dict(stock=rows['stock'],corrected=rows['corrected'],fixtureHashes=pins,fixtureUnchanged=True,exportNames=[name],exportCount=functions,exportRva=rva,guestOutputSha256=sha(guest),bothRelocationTargetsVerified=True,noGuestExecution=True)
|
||||
(out/'results.json').write_text(json.dumps(result,indent=2)+'\n');print('GENERIC_STOCK_FAIL_CORRECTED_PASS_SAME_FIXTURE')
|
||||
if __name__=='__main__':main()
|
||||
Loading…
Reference in New Issue