mirror of
https://github.com/cheat-engine/cheat-engine
synced 2026-08-15 02:26:08 -04:00
1087 lines
27 KiB
ObjectPascal
Executable file
1087 lines
27 KiB
ObjectPascal
Executable file
unit binutils;
|
|
|
|
//responsible for launching and retrieving data from the binutil tools
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$warn 2005 off}
|
|
|
|
interface
|
|
|
|
uses
|
|
{$ifdef darwin}macport,{$endif}
|
|
{$ifdef windows}windows, {$endif}
|
|
newkernelhandler, forms, Classes, SysUtils, process, LastDisassembleData, strutils, maps;
|
|
|
|
type
|
|
TSection=record
|
|
name: string;
|
|
size: dword;
|
|
end;
|
|
TSectionArray=array of TSection;
|
|
|
|
TImportListEntry=record
|
|
name: string;
|
|
address: ptruint;
|
|
end;
|
|
|
|
TImportList=array of TImportListEntry;
|
|
|
|
|
|
TBinarySection=record
|
|
sectionname: string;
|
|
data: array of byte;
|
|
end;
|
|
TBinarySections=array of TBinarySection;
|
|
|
|
type
|
|
TBinUtils=class
|
|
private
|
|
disassemblercache: TMap;
|
|
fpath: string;
|
|
procedure setPath(path: string);
|
|
procedure cleanEntry(_entry: Pointer);
|
|
public
|
|
name: string;
|
|
description: string;
|
|
prefix: string;
|
|
lasterror: string;
|
|
arch: string;
|
|
OnDisassemble: integer; //lua function reference
|
|
|
|
ASParam: string;
|
|
LDParam: string;
|
|
OBJDUMPParam: string;
|
|
DisassemblerCommentChar: string;
|
|
|
|
constructor create;
|
|
destructor destroy; override;
|
|
|
|
procedure clearDisassemblerCache;
|
|
procedure clearLongRangeDisassemblerCache(address: ptruint);
|
|
|
|
|
|
function compile(script: tstringlist): boolean;
|
|
function compileproject(filelist: tstringlist): boolean;
|
|
|
|
procedure LinkObjectFiles(objectfiles: tstringlist; const imports: TImportList; targetfilename: string; targetaddress:ptruint=0);
|
|
procedure ObjectToBinary(objectfile: string; binfile: string);
|
|
procedure stripSection(objectfile: string; section: string);
|
|
|
|
function assemble(script: tstringlist; extraparams: string; out filename: string): boolean;
|
|
procedure nm(filename: string; defined: tstringlist; undefined: tstringlist; registersymbols: boolean=false);
|
|
procedure GetSections(filename: string; var sections: TSectionArray);
|
|
procedure ldAndExtract(objectfile, linkfilename, extraparams: string; imports: TImportList; var sections: TBinarySections);
|
|
procedure ldAndInject(objectfiles: tstringlist);
|
|
procedure disassemble(var ldd: TLastDisassembleData); //address and bytes are filled in
|
|
published
|
|
property path: string read fpath write setPath;
|
|
end;
|
|
|
|
|
|
var
|
|
binutilslist: TList;
|
|
defaultBinutils: TBinUtils; //used by the disassembler and single line assember if set (gui thing)
|
|
|
|
|
|
implementation
|
|
|
|
uses dialogs, lua, LuaHandler, ProcessHandlerUnit, symbolhandler;
|
|
|
|
type
|
|
TDisassemblerCacheEntry=record
|
|
bytes: pbyte;
|
|
bytesize: integer;
|
|
instruction: pchar;
|
|
parameter: pchar;
|
|
extra: pchar;
|
|
end;
|
|
|
|
PDisassemblerCacheEntry=^TDisassemblerCacheEntry;
|
|
|
|
constructor TBinUtils.create;
|
|
begin
|
|
disassemblercache:=TMap.Create(ituPtrSize,sizeof(TDisassemblerCacheEntry));
|
|
|
|
end;
|
|
|
|
destructor TBinUtils.destroy;
|
|
begin
|
|
clearDisassemblerCache;
|
|
disassemblercache.free;
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TBinUtils.cleanEntry(_entry: pointer);
|
|
var entry: PDisassemblerCacheEntry;
|
|
begin
|
|
if _entry=nil then exit;
|
|
|
|
entry:=_entry;
|
|
if entry^.bytes<>nil then
|
|
begin
|
|
FreeMemAndNil(entry^.bytes);
|
|
|
|
end;
|
|
|
|
if entry^.instruction<>nil then
|
|
begin
|
|
strdispose(entry^.instruction);
|
|
entry^.instruction:=nil;
|
|
end;
|
|
|
|
if entry^.parameter<>nil then
|
|
begin
|
|
strdispose(entry^.parameter);
|
|
entry^.parameter:=nil;
|
|
end;
|
|
|
|
if entry^.extra<>nil then
|
|
begin
|
|
strdispose(entry^.extra);
|
|
entry^.extra:=nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinUtils.clearLongRangeDisassemblerCache(address: ptruint);
|
|
var
|
|
it: TMapIterator;
|
|
entry: PDisassemblerCacheEntry;
|
|
|
|
id: ptruint;
|
|
|
|
mina: ptruint;
|
|
maxa: ptruint;
|
|
|
|
begin
|
|
//clear all entries that are 25000 bytes before after address (leaving about 50000 out of 100000 entries)
|
|
if address>25000 then
|
|
mina:=address-25000;
|
|
|
|
if mina>address then //overflow
|
|
mina:=0;
|
|
|
|
maxa:=address+25000;
|
|
if maxa<address then
|
|
maxa:={$ifdef cpu32}qword($ffffffff){$else}qword($ffffffffffffffff){$endif};
|
|
|
|
it:=TMapIterator.Create(disassemblercache);
|
|
try
|
|
it.first;
|
|
while not it.eom do
|
|
begin
|
|
entry:=it.DataPtr;
|
|
it.GetID(id);
|
|
|
|
if (id<mina) or (id>maxa) then
|
|
begin
|
|
cleanentry(entry);
|
|
disassemblercache.Delete(id);
|
|
end;
|
|
|
|
it.next;
|
|
end;
|
|
finally
|
|
it.free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinUtils.clearDisassemblerCache; //full clear
|
|
var it: TMapIterator;
|
|
entry: PDisassemblerCacheEntry;
|
|
begin
|
|
it:=TMapIterator.Create(disassemblercache);
|
|
try
|
|
it.First;
|
|
while not it.EOM do
|
|
begin
|
|
entry:=it.DataPtr;
|
|
cleanEntry(entry);
|
|
it.Next;
|
|
end;
|
|
|
|
finally
|
|
it.free;
|
|
end;
|
|
|
|
disassemblercache.Clear;
|
|
end;
|
|
|
|
procedure TBinUtils.setPath(path: string);
|
|
begin
|
|
fpath:=path;
|
|
if length(fpath)>0 then
|
|
begin
|
|
if fpath[length(fpath)]<>DirectorySeparator then
|
|
fpath:=fpath+DirectorySeparator;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinUtils.disassemble(var ldd: TLastDisassembleData);
|
|
var entry: TDisassemblerCacheEntry;
|
|
p: TProcess;
|
|
|
|
i,j: integer;
|
|
|
|
output: TStringStream;
|
|
r: tstringlist;
|
|
t: integer;
|
|
|
|
s: string;
|
|
|
|
buffer: pbyte;
|
|
br: ptruint;
|
|
|
|
f: string;
|
|
fs: tfilestream;
|
|
|
|
line1, line2: string;
|
|
|
|
a1, a2: string;
|
|
address1, address2: int64;
|
|
instr: string;
|
|
param: string;
|
|
comment: string;
|
|
found: boolean;
|
|
|
|
luavm: PLua_state;
|
|
|
|
os: string;
|
|
params: array of string;
|
|
begin
|
|
//check if this address is in the disassembled cache, and if so, add it
|
|
luavm:=GetLuaState;
|
|
|
|
getmem(buffer, 512);
|
|
try
|
|
FillByte(buffer^, 512,0);
|
|
br:=0;
|
|
|
|
ReadProcessMemory(processhandle, pointer(ldd.address), buffer, 512, br);
|
|
|
|
if br=0 then
|
|
begin
|
|
ldd.opcode:='??';
|
|
ldd.parameters:='';
|
|
setlength(ldd.bytes,0);
|
|
exit;
|
|
end;
|
|
|
|
if disassemblercache.GetData(ldd.address, entry) then
|
|
begin
|
|
//return this data
|
|
if (entry.bytesize<512) and CompareMem(buffer, entry.bytes, entry.bytesize) then
|
|
begin
|
|
//no change in bytes, so accept it
|
|
ldd.opcode:=entry.instruction;
|
|
setlength(ldd.Bytes, entry.bytesize);
|
|
for i:=0 to entry.bytesize-1 do
|
|
ldd.bytes[i]:=entry.bytes[i];
|
|
|
|
ldd.parameters:=entry.parameter;
|
|
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
if disassemblercache.Count>100000 then
|
|
clearLongRangeDisassemblerCache(ldd.address);
|
|
|
|
|
|
//still here, disassemble this address and a couple of instructions after it as well
|
|
f:=GetTempFileName;
|
|
fs:=tfilestream.Create(f, fmCreate);
|
|
fs.WriteBuffer(buffer[0], 512);
|
|
fs.Free;
|
|
|
|
|
|
p:=TProcess.Create(Application);
|
|
output:=nil;
|
|
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.executable:='"'+fpath+prefix+'objdump'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
|
|
p.parameters.add(OBJDUMPParam);
|
|
p.parameters.add('--adjust-vma=0x'+inttohex(ldd.address,8));
|
|
p.parameters.Add('--stop-address=0x'+inttohex(ldd.address+512,8));
|
|
p.Parameters.add('--prefix-addresses');
|
|
|
|
p.Parameters.add('-z');
|
|
p.Parameters.add('-bbinary');
|
|
p.Parameters.add('-D');
|
|
p.Parameters.add('-m'+arch);
|
|
p.parameters.add('"'+f+'"');
|
|
|
|
//lua
|
|
if OnDisassemble<>0 then
|
|
begin
|
|
//LuaCS.enter;
|
|
t:=lua_gettop(luavm);
|
|
try
|
|
lua_rawgeti(luavm, LUA_REGISTRYINDEX, OnDisassemble);
|
|
lua_pushinteger(Luavm, ldd.address);
|
|
if lua_pcall(luavm, 1, 1, 0)=0 then
|
|
begin
|
|
s:=Lua_ToString(Luavm, -1);
|
|
|
|
for i:=1 to WordCount(s, [' ']) do
|
|
p.parameters.Add(ExtractWord(i,s,[' ']));
|
|
end;
|
|
finally
|
|
lua_settop(luavm, t);
|
|
end;
|
|
end;
|
|
|
|
{ try
|
|
p.Execute;
|
|
except
|
|
on e: exception do
|
|
begin
|
|
OutputDebugString(e.message);
|
|
raise;
|
|
end;
|
|
end; }
|
|
|
|
setlength(params, p.parameters.Count);
|
|
for i:=0 to p.parameters.count-1 do
|
|
params[i]:=p.Parameters[i];
|
|
|
|
if RunCommand(p.Executable, params, os,[poNoConsole]) then
|
|
begin
|
|
output:=TStringStream.create('');
|
|
output.WriteString(os);
|
|
end
|
|
else
|
|
begin
|
|
//failure to run
|
|
ldd.opcode:='Invalid';
|
|
ldd.parameters:='binutils';
|
|
setlength(ldd.bytes,0);
|
|
exit;
|
|
end;
|
|
|
|
{
|
|
output:=TStringStream.create('');
|
|
|
|
repeat
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable)
|
|
else
|
|
sleep(1);
|
|
until not p.Running;
|
|
|
|
p.WaitOnExit; //just to be sure
|
|
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable); }
|
|
|
|
{if p.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
|
|
raise exception.create(lasterror);
|
|
end;}
|
|
|
|
//parse the output
|
|
r:=tstringlist.create;
|
|
try
|
|
output.position:=0;
|
|
r.loadfromstream(output);
|
|
|
|
|
|
found:=false;
|
|
for i:=0 to r.count-2 do
|
|
begin
|
|
line1:=r[i];
|
|
line2:=r[i+1];
|
|
|
|
if (copy(line1, 1,2)='0x') and (copy(line1, 1,2)='0x') then
|
|
begin
|
|
a1:=ExtractWord(1, line1, [' ',#8,#9]);
|
|
a2:=ExtractWord(1, line2, [' ',#8,#9]);
|
|
|
|
if TryStrToInt64(a1,address1) and TryStrToInt64(a2,address2) then
|
|
begin
|
|
instr:=ExtractWord(2, line1, [' ',#8,#9]);
|
|
param:='';
|
|
comment:='';
|
|
j:=WordPosition(3, line1, [' ',#8,#9]);
|
|
if j>0 then
|
|
param:=copy(line1, j, length(line1));
|
|
|
|
if DisassemblerCommentChar<>'' then
|
|
begin
|
|
j:=pos(DisassemblerCommentChar, param);
|
|
if j>0 then
|
|
begin
|
|
comment:=copy(param, j, length(param));
|
|
param:=copy(param,1, j-1);
|
|
end;
|
|
end;
|
|
|
|
//valid
|
|
if disassemblercache.GetData(address1, entry) then
|
|
begin
|
|
cleanEntry(@entry);
|
|
disassemblercache.Delete(address1);
|
|
end;
|
|
|
|
FillByte(entry,sizeof(entry),0);
|
|
|
|
entry.bytesize:=address2-address1;
|
|
if (entry.bytesize>64) or (entry.bytesize<=0) then
|
|
break;
|
|
|
|
getmem(entry.bytes, entry.bytesize);
|
|
|
|
if entry.bytes=nil then
|
|
break;
|
|
|
|
for j:=0 to entry.bytesize-1 do
|
|
entry.bytes[j]:=buffer[address1-ldd.address+j];
|
|
|
|
entry.instruction:=strnew(pchar(instr));
|
|
entry.parameter:=strnew(pchar(param));
|
|
entry.extra:=strnew(pchar(comment));
|
|
|
|
disassemblercache.Add(address1, entry);
|
|
|
|
if not found then
|
|
begin
|
|
//fill it in
|
|
ldd.opcode:=instr;
|
|
setlength(ldd.Bytes, entry.bytesize);
|
|
for j:=0 to entry.bytesize-1 do
|
|
ldd.bytes[j]:=$90; //entry.bytes[j];
|
|
|
|
ldd.parameters:=param;
|
|
found:=true;
|
|
end;
|
|
end;
|
|
end;
|
|
end; //for
|
|
|
|
if not found then
|
|
begin
|
|
ldd.opcode:='??';
|
|
ldd.parameters:='';
|
|
setlength(ldd.bytes,0);
|
|
end;
|
|
|
|
finally
|
|
r.free;
|
|
end;
|
|
|
|
|
|
|
|
finally
|
|
deletefile(f);
|
|
if output<>nil then
|
|
output.free;
|
|
|
|
p.free;
|
|
end;
|
|
|
|
|
|
finally
|
|
if buffer<>nil then
|
|
FreeMemAndNil(buffer);
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TbinUtils.ObjectToBinary(objectfile: string; binfile: string);
|
|
var objcopyprocess: tprocess;
|
|
begin
|
|
objcopyprocess:=TProcess.create(nil);
|
|
objcopyprocess.CurrentDirectory:=fpath;
|
|
objcopyprocess.executable:='"'+fpath+prefix+'objcopy'+'"';
|
|
objcopyprocess.Options:=[poUsePipes, poNoConsole];
|
|
|
|
objcopyprocess.Parameters.Add('-Obinary');
|
|
objcopyprocess.Parameters.Add(objectfile);
|
|
objcopyprocess.Parameters.Add(binfile);
|
|
|
|
objcopyprocess.Execute;
|
|
if objcopyprocess.WaitOnExit then
|
|
begin
|
|
if objcopyprocess.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, objcopyprocess.Stderr.NumBytesAvailable+1);
|
|
objcopyprocess.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create('ObjectToBinary: objcopy:'+lasterror);
|
|
end;
|
|
end
|
|
else
|
|
raise exception.create('ObjectToBinary: Wait error on objcopy');
|
|
end;
|
|
|
|
procedure TBinUtils.LinkObjectFiles(objectfiles: tstringlist; const imports: TImportList; targetfilename: string; targetaddress:ptruint=0);
|
|
var
|
|
p: tprocess;
|
|
i: integer;
|
|
|
|
begin
|
|
if targetaddress=$00400000 then targetaddress:=$00400010; //does not work with 0x00400000
|
|
p:=TProcess.create(nil);
|
|
p.CurrentDirectory:=fpath;
|
|
p.executable:='"'+fpath+prefix+'ld'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
p.Parameters.Add('-static');
|
|
p.parameters.add(LDParam); //-mi386pe for 32-bit
|
|
p.Parameters.Add('-T NUL');
|
|
if targetaddress<>0 then
|
|
p.parameters.add('-Ttext 0x'+inttohex(targetaddress,1));
|
|
|
|
for i:=0 to objectfiles.count-1 do
|
|
p.Parameters.Add(objectfiles[i]);
|
|
|
|
for i:=0 to length(imports)-1 do
|
|
p.Parameters.Add('--defsym='+imports[i].name+'=0x'+inttohex(imports[i].address,8));
|
|
|
|
p.Parameters.Add('-o "'+targetfilename+'"');
|
|
|
|
p.Execute;
|
|
if p.WaitOnExit then
|
|
begin
|
|
if p.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
raise exception.create(lasterror);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TBinUtils.ldAndInject(objectfiles: tstringlist);
|
|
var
|
|
p: tprocess;
|
|
|
|
m: tmemorystream;
|
|
z: tfilestream;
|
|
size,size2: integer;
|
|
i,j: integer;
|
|
tempfile: string='';
|
|
imports: TImportList;
|
|
|
|
defined, undefined: tstringlist;
|
|
found: boolean;
|
|
mem: pointer=nil;
|
|
x: ptruint;
|
|
begin
|
|
//first get all the imports
|
|
defined:=tstringlist.create;
|
|
undefined:=tstringlist.create;
|
|
try
|
|
for i:=0 to objectfiles.count-1 do
|
|
nm(objectfiles[i], defined, undefined);
|
|
|
|
//go through the undefined list and fill them in (where possible)
|
|
setlength(imports,0);
|
|
for i:=0 to undefined.count-1 do
|
|
begin
|
|
found:=false;
|
|
for j:=0 to length(imports)-1 do
|
|
begin
|
|
if imports[j].name=undefined[i] then
|
|
begin
|
|
found:=true;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
if not found then
|
|
begin
|
|
j:=length(imports);
|
|
setlength(imports, j+1);
|
|
imports[j].name:=undefined[i];
|
|
imports[j].address:=symhandler.getAddressFromName(undefined[i]); //error out if not found
|
|
end;
|
|
end;
|
|
finally
|
|
defined.free;
|
|
undefined.free;
|
|
end;
|
|
|
|
//still here so the undefinedlist has been handled
|
|
|
|
tempfile:=GetTempFileName;
|
|
LinkObjectFiles(objectfiles, imports, tempfile );
|
|
|
|
ObjectToBinary(tempfile, tempfile+'.bin');
|
|
z:=tfilestream.create(tempfile+'.bin', fmOpenRead);
|
|
size:=z.Size+4096;
|
|
z.free;
|
|
|
|
if size=0 then raise exception.create('Failure getting filesize');
|
|
|
|
|
|
//still here so linking was succesful
|
|
|
|
repeat
|
|
size2:=size;
|
|
if mem<>nil then VirtualFreeEx(processhandle, mem, 0,MEM_RELEASE);
|
|
mem:=VirtualAllocEx(processhandle, nil,size,mem_reserve or mem_commit, PAGE_EXECUTE_READWRITE);
|
|
if mem=nil then raise exception.create('Failure allocating memory');
|
|
LinkObjectFiles(objectfiles, imports, tempfile, ptruint(mem) );
|
|
ObjectToBinary(tempfile, tempfile+'.bin');
|
|
|
|
z:=tfilestream.create(tempfile+'.bin' ,fmOpenRead);
|
|
size:=z.Size+4096;
|
|
z.free;
|
|
until size<=size2; //repeat linking until the proper size is found (at most 2)
|
|
|
|
|
|
m:=tmemorystream.create;
|
|
m.loadfromfile(tempfile+'.bin');
|
|
WriteProcessMemory(processhandle, mem, m.memory, m.size,x);
|
|
m.free;
|
|
|
|
defined.clear;
|
|
undefined.clear;
|
|
nm(tempfile, defined, undefined, true);
|
|
end;
|
|
|
|
procedure TBinUtils.ldAndExtract(objectfile, linkfilename, extraparams: string; imports: TImportList; var sections: TBinarySections); //for gnu assembler
|
|
var
|
|
p: TProcess;
|
|
i: integer;
|
|
|
|
linkedfilename: string;
|
|
bindata: TMemoryStream;
|
|
begin
|
|
p:=TProcess.create(nil);
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.executable:='"'+fpath+prefix+'ld'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
p.Parameters.add(extraparams);
|
|
p.parameters.add(LDParam);
|
|
p.Parameters.Add('-T "'+linkfilename+'"');
|
|
p.Parameters.Add('"'+objectfile+'"');
|
|
|
|
for i:=0 to length(imports)-1 do
|
|
p.Parameters.Add('--defsym='+imports[i].name+'=0x'+inttohex(imports[i].address,8));
|
|
|
|
linkedfilename:=GetTempFileName;
|
|
|
|
p.parameters.add('-o "'+linkedfilename+'"');
|
|
|
|
p.Execute;
|
|
p.WaitOnExit;
|
|
|
|
if p.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create(lasterror);
|
|
end;
|
|
|
|
|
|
|
|
finally
|
|
p.free;
|
|
end;
|
|
|
|
for i:=0 to length(sections)-1 do
|
|
begin
|
|
p:=TProcess.Create(nil);
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.executable:='"'+fpath+prefix+'objcopy'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
|
|
p.Parameters.Add('-Obinary');
|
|
p.Parameters.Add('-j'+sections[i].sectionname);
|
|
p.Parameters.Add(linkedfilename);
|
|
p.Parameters.Add(linkedfilename+'.bin');
|
|
|
|
// showmessage(p.Parameters.Text);
|
|
|
|
p.Execute;
|
|
p.WaitOnExit;
|
|
|
|
if p.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create(lasterror);
|
|
end;
|
|
|
|
bindata:=TMemoryStream.create;
|
|
try
|
|
bindata.LoadFromFile(linkedfilename+'.bin');
|
|
setlength(sections[i].data, bindata.Size);
|
|
bindata.Position:=0;
|
|
bindata.ReadBuffer(sections[i].data[0], bindata.Size);
|
|
finally
|
|
bindata.free;
|
|
end;
|
|
finally
|
|
p.free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinUtils.GetSections(filename: string; var sections: TSectionArray);
|
|
var
|
|
output: TStringStream;
|
|
p: TProcess;
|
|
ec: integer;
|
|
|
|
i,j: integer;
|
|
|
|
r: TStringList;
|
|
|
|
idx, _name, size: string;
|
|
|
|
s: string;
|
|
begin
|
|
p:=TProcess.Create(nil);
|
|
output:=nil;
|
|
r:=nil;
|
|
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.Executable:='"'+fpath+prefix+'objdump'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
|
|
p.Parameters.Add('-h');
|
|
p.Parameters.Add('"'+filename+'"');
|
|
|
|
p.Execute;
|
|
|
|
|
|
output:=TStringStream.create('');
|
|
|
|
repeat
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable)
|
|
else
|
|
sleep(1);
|
|
until not p.Running;
|
|
|
|
p.WaitOnExit; //just to be sure
|
|
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable);
|
|
|
|
if p.ExitCode<>0 then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create(lasterror);
|
|
end;
|
|
|
|
r:=TStringList.create;
|
|
output.position:=0;
|
|
r.LoadFromStream(output);
|
|
|
|
//search for lines with: integer, string, hexvalue
|
|
for i:=0 to r.Count-1 do
|
|
begin
|
|
s:=trim(r[i]);
|
|
|
|
if WordCount(r[i],[' ',#8])>3 then
|
|
begin
|
|
idx:=ExtractWord(1, s,[' ',#8]);
|
|
|
|
if TryStrToInt(idx, j) then
|
|
begin
|
|
_name:=ExtractWord(2, s,[' ',#8]);
|
|
size:=ExtractWord(3, s,[' ',#8]);
|
|
|
|
if TryStrToInt('0x'+size, j) then
|
|
begin
|
|
if (j>0) then
|
|
begin
|
|
setlength(sections, length(sections)+1);
|
|
sections[length(sections)-1].name:=_name;
|
|
sections[length(sections)-1].size:=j;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
|
|
end;
|
|
|
|
finally
|
|
if r<>nil then
|
|
r.free;
|
|
|
|
if output<>nil then
|
|
output.free;
|
|
|
|
if p<>nil then
|
|
p.free;
|
|
end;
|
|
|
|
|
|
end;
|
|
|
|
procedure TBinUtils.nm(filename: string; defined: tstringlist; undefined: tstringlist; registersymbols: boolean=false);
|
|
{
|
|
Executes nm and keeps a list of all defined and undefined symbols
|
|
The call can be done with a defined and undefined list already filled in. In that case, just update
|
|
}
|
|
var
|
|
p: TProcess;
|
|
ec: integer;
|
|
|
|
output: TStringStream;
|
|
s: tstringlist;
|
|
i,j: integer;
|
|
line: string;
|
|
|
|
str: string;
|
|
a: ptruint;
|
|
e: integer;
|
|
begin
|
|
output:=TStringStream.Create('');
|
|
p:=TProcess.Create(nil);
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.Executable:='"'+fpath+prefix+'nm'+'"';
|
|
p.Options:=[poUsePipes, poNoConsole];
|
|
p.Parameters.Add('"'+filename+'"');
|
|
|
|
p.Execute;
|
|
|
|
repeat
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable)
|
|
else
|
|
sleep(1);
|
|
until not p.Running;
|
|
|
|
p.WaitOnExit; //just to be sure
|
|
|
|
if p.Output.NumBytesAvailable>0 then
|
|
output.CopyFrom(p.Output, p.Output.NumBytesAvailable);
|
|
|
|
|
|
ec:=p.ExitCode;
|
|
if ec=0 then
|
|
begin
|
|
s:=tstringlist.create;
|
|
output.Position:=0;
|
|
s.LoadFromStream(output);
|
|
for i:=0 to s.Count-1 do
|
|
begin
|
|
line:=trim(s[i]);
|
|
if (length(line)>2) then
|
|
begin
|
|
if (line[1]='U') then
|
|
begin
|
|
str:=copy(line, 3, length(line));
|
|
|
|
if (defined.IndexOf(str)<>-1) or (undefined.IndexOf(str)<>-1) then continue; //it's already defined somewhere else or already in the undefined list
|
|
undefined.add(str); //add it to the undefined list
|
|
end
|
|
else
|
|
begin
|
|
str:=copy(line, RPos(' ',line)+1, length(line));
|
|
j:=undefined.IndexOf(str); //check if it was undefined previously
|
|
if j<>-1 then undefined.Delete(j); //yes, found it
|
|
|
|
defined.add(str);
|
|
{
|
|
if registersymbols then
|
|
begin
|
|
j:=pos(' ',line);
|
|
if j>0 then
|
|
begin
|
|
str:=copy(line,1,j-1);
|
|
a:=StrToInt64('$'+str);
|
|
|
|
|
|
end;
|
|
end; }
|
|
end;
|
|
end;
|
|
end;
|
|
s.free;
|
|
end
|
|
else
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create(lasterror);
|
|
end;
|
|
finally
|
|
p.free;
|
|
output.free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinUtils.stripSection(objectfile: string; section: string);
|
|
var
|
|
p: TProcess;
|
|
begin
|
|
p:=tprocess.create(nil);
|
|
p.CurrentDirectory:=fpath;
|
|
p.Executable:='"'+fpath+prefix+'objcopy'+'"';
|
|
p.Options:=[poUsePipes, poWaitOnExit, poNoConsole];
|
|
p.parameters.add('--remove-section '+section);
|
|
p.Execute;
|
|
p.WaitOnExit;
|
|
|
|
p.free;
|
|
end;
|
|
|
|
function TBinUtils.compile(script: tstringlist): boolean;
|
|
//compiles the given C code , allocates memory for it, links it, and then copies it to the allocated code, and registers the symbols
|
|
//single scripts only. (no multifile supported. Use compileproject for that)
|
|
|
|
//On hold. This needs to be rewritten to be dynamic instead of static and then fill in the relocation table and importtable (my ld can't link above 0x100000000)
|
|
|
|
//fuck it, just compile a dll and then force load the dll by parsing the import table
|
|
var
|
|
p: TProcess;
|
|
inputfile: string='';
|
|
outputfile: string='';
|
|
|
|
i,j: integer;
|
|
found: boolean;
|
|
outputfilelist: tstringlist;
|
|
begin
|
|
result:=false;
|
|
p:=tprocess.create(nil);
|
|
inputfile:=GetTempFileName;
|
|
DeleteFile(inputfile+'.c');
|
|
DeleteFile(outputfile+'.o');
|
|
if renamefile(inputfile,inputfile+'.c') then
|
|
begin
|
|
outputfile:=inputfile+'.o';
|
|
inputfile:=inputfile+'.c';
|
|
|
|
try
|
|
script.SaveToFile(inputfile);
|
|
p.CurrentDirectory:=fpath;
|
|
p.Executable:='"'+fpath+prefix+'gcc'+'"';
|
|
p.Options:=[poUsePipes, poWaitOnExit, poNoConsole];
|
|
p.parameters.add('-c "'+inputfile+'"');
|
|
p.parameters.add('-o "'+outputfile+'"');
|
|
p.parameters.add(outputfile);
|
|
|
|
p.Execute;
|
|
if p.WaitOnExit then
|
|
begin
|
|
if p.exitcode=0 then //compilation success
|
|
begin
|
|
stripSection(outputfile,'.pdata'); //the mingw I use adds a .pdata section which does not like 64-bit
|
|
|
|
outputfilelist:=tstringlist.create;
|
|
outputfilelist.add(outputfile);
|
|
ldAndInject(outputfilelist);
|
|
outputfilelist.free;
|
|
end;
|
|
end;
|
|
finally
|
|
if inputfile<>'' then
|
|
deletefile(inputfile);
|
|
|
|
if outputfile<>'' then
|
|
deletefile(outputfile);
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
function TBinUtils.compileproject(filelist: tstringlist): boolean;
|
|
begin
|
|
result:=false;
|
|
end;
|
|
|
|
function TBinUtils.assemble(script: tstringlist; extraparams: string; out filename: string): boolean;
|
|
var
|
|
p: TProcess;
|
|
origfile: string;
|
|
ec: integer;
|
|
begin
|
|
p:=TProcess.Create(nil);
|
|
origfile:=GetTempFileName;
|
|
script.SaveToFile(origfile);
|
|
|
|
filename:=GetTempFileName;
|
|
|
|
|
|
try
|
|
p.CurrentDirectory:=fpath;
|
|
p.Executable:='"'+fpath+prefix+'as'+'"';
|
|
p.Options:=[poUsePipes, poWaitOnExit, poNoConsole];
|
|
|
|
p.Parameters.add(extraparams);
|
|
p.parameters.add(ASParam);
|
|
p.Parameters.Add('"'+origfile+'"');
|
|
p.Parameters.Add('-o "'+filename+'"');
|
|
|
|
p.Execute;
|
|
p.WaitOnExit;
|
|
|
|
ec:=p.ExitCode;
|
|
|
|
result:=ec=0;
|
|
|
|
if not result then
|
|
begin
|
|
setlength(lasterror, p.Stderr.NumBytesAvailable+1);
|
|
p.Stderr.ReadBuffer(lasterror[1], length(lasterror)-1);
|
|
lasterror[length(lasterror)]:=#0;
|
|
|
|
raise exception.create(lasterror);
|
|
end;
|
|
|
|
finally
|
|
p.free;
|
|
deletefile(origfile);
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
binutilslist:=Tlist.create;
|
|
|
|
{
|
|
--[[
|
|
Registers the ARM binutils with CE
|
|
--]]
|
|
|
|
local armconfig={}
|
|
armconfig.Name="ARM" --set the name if there are multiple binutils for whatever reason
|
|
armconfig.Description="BinUtils for ARM" -- ^
|
|
armconfig.Architecture="arm" --used by the objdump -m<architecture>
|
|
armconfig.OnDisassemble=function(address)
|
|
if (address & 1)==1 then
|
|
return "-Mforce-thumb"
|
|
end
|
|
end
|
|
armconfig.Path=getCheatEngineDir()..[[binutils\arm\bin]]
|
|
armconfig.Prefix="arm-linux-androideabi-"
|
|
|
|
registerBinUtil(armconfig)
|
|
}
|
|
|
|
finalization
|
|
if binutilslist <> nil then
|
|
binutilslist.Free;
|
|
|
|
end.
|
|
|