cheat-engine/Cheat Engine/binutils.pas

700 lines
16 KiB
ObjectPascal

unit binutils;
//responsible for launching and retrieving data from the binutil tools
{$mode objfpc}{$H+}
interface
uses
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;
function assemble(script: tstringlist; extraparams: string; out filename: string): boolean;
procedure nm(filename: string; undefined: tstringlist);
procedure GetSections(filename: string; var sections: TSectionArray);
procedure ldAndExtract(objectfile, linkfilename, extraparams: string; imports: TImportList; var sections: TBinarySections);
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, NewKernelHandler, ProcessHandlerUnit;
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
freemem(entry^.bytes);
if entry^.instruction<>nil then
strdispose(entry^.instruction);
if entry^.parameter<>nil then
strdispose(entry^.parameter);
if entry^.extra<>nil then
strdispose(entry^.extra);
end;
procedure TBinUtils.clearDisassemblerCache;
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;
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;
begin
//check if this address is in the disassembled cache, and if so, add it
getmem(buffer, 512);
try
FillByte(buffer^, 512,0);
ReadProcessMemory(processhandle, pointer(ldd.address), buffer, 512, br);
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>10000 then
clearDisassemblerCache;
//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(nil);
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;
try
t:=lua_gettop(luavm);
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;
lua_settop(luavm, t);
finally
luacs.leave;
end;
end;
try
p.Execute;
except
on e: exception do
begin
OutputDebugString(e.message);
raise;
end;
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(disassemblercache.GetDataPtr(address1));
disassemblercache.Delete(address1);
end;
entry.bytesize:=address2-address1;
getmem(entry.bytes, entry.bytesize);
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]:=entry.bytes[j];
ldd.parameters:=param;
found:=true;
end;
end;
end;
end;
finally
r.free;
end;
finally
deletefile(f);
if output<>nil then
output.free;
p.free;
end;
finally
if buffer<>nil then
freemem(buffer);
end;
end;
procedure TBinUtils.ldAndExtract(objectfile, linkfilename, extraparams: string; imports: TImportList; var sections: TBinarySections);
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; undefined: tstringlist);
var
p: TProcess;
ec: integer;
output: TStringStream;
s: tstringlist;
i: integer;
line: string;
begin
output:=TStringStream.Create('');
p:=TProcess.Create(nil);
try
p.CurrentDirectory:=fpath;
p.Executable:='"'+fpath+prefix+'nm'+'"';
p.Options:=[poUsePipes, poNoConsole];
p.Parameters.Add('-u');
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) and (line[1]='U') then
undefined.add(copy(line, 3, length(line)));
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;
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)
}
end.