ASMInline.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. unit ASMInline;
  2. interface
  3. {ASM Inliner
  4. Nicholas Sherlock
  5. This is incomplete, I've only implemented enough to support InnoCallback.
  6. Instructions are stored in a TMemoryStream internally
  7. Instructions usually accept some combination of Registers, Immediates and
  8. Memory References. Memory References can either be of the simple form [EAX]
  9. (Where [EAX] is really a Delphi set), or the user can build the address with
  10. the TASMInline.addr() function. It'd be nice to have a function which builds
  11. the address from a string, too, allowing the more intuitive '[EAX+EBX*4+1000]'
  12. style.
  13. The generation of instruction-relative addresses generates Relocations, using
  14. SaveToMemory() automatically rebases using the relocations to make these correct
  15. in the final block of memory.
  16. !!!! Not all special cases have been implemented in WriteRegRef().
  17. Further reduced to just what's needed for Inno Setup by Martijn Laan
  18. Added x64 support by Martijn Laan
  19. }
  20. {$IFNDEF CPUX86}
  21. {$IFNDEF CPUX64}
  22. {$MESSAGE ERROR 'This needs updating for non-x86/x64 builds'}
  23. {$ENDIF}
  24. {$ENDIF}
  25. {$IFDEF CPUX86}
  26. {$WARN IMPLICIT_INTEGER_CAST_LOSS OFF}
  27. {$WARN IMPLICIT_CONVERSION_LOSS OFF}
  28. {$ENDIF}
  29. uses Sysutils, Windows, Classes, Contnrs;
  30. type
  31. {$IFDEF CPUX86}
  32. TModMode = (mmNaked, mmDeref, mmDisp8, mmDisp32);
  33. TRegister32 = (EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI);
  34. TMemSize = (ms8, ms16, ms32, ms64);
  35. TMemoryAddress = record
  36. Size: TMemSize;
  37. UseBase: Boolean;
  38. Base, Index: TRegister32;
  39. Offset: Integer;
  40. Scale: Byte;
  41. end;
  42. EOperandSizeMismatch = class(Exception)
  43. public
  44. constructor Create;
  45. end;
  46. TRelocType = (rt32Bit);
  47. TReloc = class
  48. public
  49. Position: Cardinal;
  50. RelocType: TRelocType;
  51. end;
  52. {$ELSE}
  53. TRegister64 = (RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14, R15);
  54. {$ENDIF}
  55. TASMInline = class
  56. private
  57. FBuffer: TMemoryStream;
  58. {$IFDEF CPUX86}
  59. FRelocs: TObjectList;
  60. FBase: Cardinal;
  61. procedure AddRelocation(position: Cardinal; relocType: TRelocType);
  62. function GetReloc(index: Integer): TReloc;
  63. function RelocCount: Integer;
  64. property Relocs[index: Integer]: TReloc read GetReloc;
  65. procedure WriteRegRef(reg: byte; base: TRegister32; deref: Boolean; index: TRegister32; Offset: Integer; Scale: byte; usebase: Boolean); overload;
  66. procedure WriteRegRef(mem: TMemoryAddress; reg: TRegister32); overload;
  67. procedure WriteRegRef(reg: TRegister32; base: TRegister32; deref: Boolean; index: TRegister32 = EAX; Offset: Integer = 0; Scale: byte = 0; usebase: Boolean = true); overload;
  68. procedure Relocate(base: pointer);
  69. {$ELSE}
  70. function RegCode(const R: TRegister64): Byte;
  71. procedure WriteREX(const W, R, X, B: Boolean);
  72. {$ENDIF}
  73. procedure WriteByte(const B: Byte);
  74. procedure WriteInteger(const I: Integer);
  75. {$IFNDEF CPUX86}
  76. procedure WriteUInt64(const U: UInt64);
  77. {$ENDIF}
  78. public
  79. constructor Create;
  80. destructor Destroy; override;
  81. function SaveAsMemory: Pointer;
  82. function Size: Integer;
  83. {$IFDEF CPUX86}
  84. function Addr(base: TRegister32; offset: Integer; size: TMemSize = ms32): TMemoryAddress; overload;
  85. //PUSH reg
  86. procedure Push(reg: TRegister32); overload;
  87. //POP reg
  88. procedure Pop(reg: TRegister32);
  89. //JUMP rel32
  90. procedure Jmp(target: pointer); overload;
  91. //MOV reg, imm
  92. procedure Mov(reg: TRegister32; b: longword); overload;
  93. //MOV reg, mem and MOV mem, reg
  94. procedure Mov(mem: TMemoryAddress; reg: TRegister32); overload;
  95. procedure Mov(reg: TRegister32; mem: TMemoryAddress); overload;
  96. {$ELSE}
  97. procedure MovRegReg(const Dest, Src: TRegister64);
  98. procedure MovRegImm64(const Dest: TRegister64; const Value: UInt64);
  99. procedure MovRegMemRSP(const Dest: TRegister64; const Disp: Integer);
  100. procedure MovMemRSPReg(const Disp: Integer; const Src: TRegister64);
  101. procedure SubRsp(const Amount: Integer);
  102. procedure AddRsp(const Amount: Integer);
  103. procedure CallReg(const Reg: TRegister64);
  104. procedure Ret;
  105. {$ENDIF}
  106. end;
  107. implementation
  108. {$IFDEF CPUX86}
  109. constructor EOperandSizeMismatch.create;
  110. begin
  111. inherited Create('Operand size mismatch');
  112. end;
  113. {Throw an exception if test<>match. Poor man's assert().
  114. Could overload to add other sorts of tests}
  115. procedure require(test: TMemSize; match: TMemSize);
  116. begin
  117. if test <> match then
  118. raise EOperandSizeMismatch.Create;
  119. end;
  120. function regnum(reg: TRegister32): byte; overload;
  121. begin
  122. case reg of
  123. EAX: result := 0;
  124. EBX: result := 3;
  125. ECX: result := 1;
  126. EDX: result := 2;
  127. ESP: result := 4;
  128. EBP: result := 5;
  129. ESI: result := 6;
  130. EDI: result := 7;
  131. else
  132. raise Exception.create('Unknown register...');
  133. end;
  134. end;
  135. function ModModeNum(m: TModMode): byte;
  136. begin
  137. case m of
  138. mmDeref: result := 0;
  139. mmDisp8: result := 1;
  140. mmDisp32: result := 2;
  141. mmNaked: result := 3;
  142. else
  143. raise Exception.create('Invalid mod mode: ' + inttostr(ord(m)));
  144. end;
  145. end;
  146. function EncodeSIB(scale, index, base: byte): byte;
  147. begin
  148. result := byte(base or (index shl 3) or (scale shl 6));
  149. end;
  150. function EncodeModRM(aMod, aReg, aRM: byte): byte; overload;
  151. begin
  152. result := byte((aMod shl 6) or (areg shl 3) or aRM);
  153. end;
  154. {$ENDIF}
  155. { TASMInline }
  156. constructor TASMInline.Create;
  157. begin
  158. FBuffer := TMemoryStream.Create;
  159. {$IFDEF CPUX86}
  160. FRelocs := TobjectList.Create;
  161. {$ENDIF}
  162. end;
  163. destructor TASMInline.Destroy;
  164. begin
  165. {$IFDEF CPUX86}
  166. FRelocs.Free;
  167. {$ENDIF}
  168. FBuffer.Free;
  169. inherited;
  170. end;
  171. {$IFDEF CPUX86}
  172. {$IFOPT R+}
  173. {$DEFINE RESTORER}
  174. {$R-}
  175. {$ENDIF}
  176. {$IFOPT Q+}
  177. {$DEFINE RESTOREQ}
  178. {$Q-}
  179. {$ENDIF}
  180. procedure TASMInline.Relocate(base: pointer);
  181. var diff, orig: integer;
  182. i: integer;
  183. reloc: TReloc;
  184. begin
  185. const oldpos = fbuffer.Position;
  186. try
  187. diff := -(longword(base) - fbase);
  188. for i := 0 to RelocCount - 1 do begin
  189. reloc := Relocs[i];
  190. case reloc.relocType of
  191. rt32Bit: begin
  192. fbuffer.Seek(reloc.position, soBeginning);
  193. fbuffer.Read(orig, sizeof(orig));
  194. fbuffer.seek(-sizeof(orig), soCurrent);
  195. orig := LongWord(orig + diff);
  196. fbuffer.write(orig, sizeof(orig));
  197. end;
  198. end;
  199. end;
  200. fbase := longword(base);
  201. finally
  202. fbuffer.position := oldpos;
  203. end;
  204. end;
  205. {$IFDEF RESTORER}
  206. {$R+}
  207. {$ENDIF}
  208. {$IFDEF RESTOREQ}
  209. {Q+}
  210. {$ENDIF}
  211. function TASMInline.GetReloc(index: integer): TReloc;
  212. begin
  213. Result := TReloc(frelocs[index]);
  214. end;
  215. function TASMInline.RelocCount: integer;
  216. begin
  217. Result := frelocs.Count;
  218. end;
  219. procedure TASMInline.AddRelocation(position: longword; relocType: TRelocType);
  220. var reloc: TReloc;
  221. begin
  222. reloc := TReloc.Create;
  223. reloc.position := position;
  224. reloc.relocType := relocType;
  225. frelocs.add(reloc);
  226. end;
  227. function TASMInline.Addr(base: TRegister32; offset: Integer; size: TMemSize = ms32): TMemoryAddress;
  228. begin
  229. result.base := base;
  230. result.scale := 0; //don't use Index
  231. result.offset := offset;
  232. result.size := size;
  233. result.usebase := true;
  234. end;
  235. procedure TASMInline.Pop(reg: TRegister32);
  236. begin
  237. writebyte($58 + regnum(reg));
  238. end;
  239. procedure TASMInline.Jmp(target: pointer);
  240. begin
  241. writebyte($E9);
  242. AddRelocation(fbuffer.position, rt32bit);
  243. WriteInteger(integer(target) - (integer(fBase) + fbuffer.Position + 4));
  244. end;
  245. procedure TASMInline.Push(reg: TRegister32);
  246. begin
  247. writebyte($50 + regnum(reg));
  248. end;
  249. procedure TASMInline.WriteRegRef(mem: TMemoryAddress; reg: TRegister32);
  250. begin
  251. writeregref(reg, mem.base, true, mem.index, mem.offset, mem.scale, mem.usebase);
  252. end;
  253. //Write the MODR/M and SIB byte for the given register or memory reference
  254. procedure TASMInline.WriteRegRef(reg: TRegister32; base: TRegister32; deref: boolean; index: TRegister32 = EAX; Offset: integer = 0; Scale: byte = 0; usebase: boolean = true);
  255. begin
  256. WriteRegRef(regnum(reg), base, deref, index, Offset, scale, usebase);
  257. end;
  258. procedure TASMInline.WriteRegRef(reg: byte; base: TRegister32; deref: boolean; index: TRegister32; Offset: integer; Scale: byte; usebase: boolean);
  259. type TOffSize = (osNone, os8, os32);
  260. var mode: TModMode;
  261. offsize: TOffSize;
  262. useSIB: boolean;
  263. areg, arm: Byte;
  264. begin
  265. if not deref then begin
  266. mode := mmNaked;
  267. offsize := osNone;
  268. end else
  269. if usebase = false then begin
  270. offsize := os32;
  271. mode := mmDeref;
  272. base := EBP; //the "no base" value
  273. end else
  274. if Offset = 0 then begin
  275. mode := mmDeref;
  276. offsize := osNone;
  277. end else
  278. if (offset >= -128) and (offset < 128) then begin //signed byte
  279. mode := mmDisp8;
  280. offsize := os8;
  281. end else begin
  282. mode := mmDisp32;
  283. offsize := os32;
  284. end;
  285. if (mode <> mmnaked) then begin
  286. usesib := (Scale > 0) or (base = ESP);
  287. end else usesib := false;
  288. if useSIB then begin //calculate scale, easiest just to use a case statement..
  289. case scale of
  290. 0: begin //dont want an index value
  291. index := ESP; //"none" value
  292. end;
  293. 1: scale := 0;
  294. 2: scale := 1;
  295. 4: scale := 2;
  296. 8: scale := 3;
  297. else raise exception.create('Invalid scale, valid values are 1,2,4,8.');
  298. end;
  299. end;
  300. if (not useSIB) and (mode = mmDeref) and (base = EBP) then begin
  301. //not available, use [EBP+0] instead
  302. mode := mmDisp8;
  303. offsize := os8;
  304. Offset := 0;
  305. end;
  306. arm := regnum(base);
  307. areg := reg;
  308. if usesib then
  309. WriteByte(EncodeModRM(ModModeNum(mode), areg, 4)) else
  310. WriteByte(EncodeModRM(ModModeNum(mode), areg, arm));
  311. if usesib then begin
  312. WriteByte(EncodeSIB(Scale, regnum(index), regnum(base)));
  313. end;
  314. //Do we have to append an offset?
  315. case offsize of
  316. os8: WriteByte(byte(offset)); //ignore sign..
  317. os32: WriteInteger(offset);
  318. end;
  319. end;
  320. procedure TASMInline.Mov(mem: TMemoryAddress; reg: TRegister32);
  321. begin
  322. require(mem.size, ms32);
  323. WriteByte($89);
  324. WriteRegRef(mem, reg);
  325. end;
  326. procedure TASMInline.Mov(reg: TRegister32; mem: TMemoryAddress);
  327. begin
  328. require(mem.size, ms32);
  329. WriteByte($8B);
  330. WriteRegRef(mem, reg);
  331. end;
  332. procedure TASMInline.Mov(reg: TRegister32; b: longword);
  333. begin
  334. writebyte($B8 + regnum(reg));
  335. writeInteger(Integer(b));
  336. end;
  337. {$ELSE}
  338. function TASMInline.RegCode(const R: TRegister64): Byte;
  339. begin
  340. Result := Byte(R);
  341. end;
  342. procedure TASMInline.WriteREX(const W, R, X, B: Boolean);
  343. begin
  344. var Prefix: Byte := $40;
  345. if W then
  346. Inc(Prefix, $08);
  347. if R then
  348. Inc(Prefix, $04);
  349. if X then
  350. Inc(Prefix, $02);
  351. if B then
  352. Inc(Prefix, $01);
  353. WriteByte(Prefix);
  354. end;
  355. procedure TASMInline.MovRegReg(const Dest, Src: TRegister64);
  356. begin
  357. const DestCode = RegCode(Dest);
  358. const SrcCode = RegCode(Src);
  359. WriteREX(True, SrcCode >= 8, False, DestCode >= 8);
  360. WriteByte($89);
  361. WriteByte(Byte($C0 or ((SrcCode and 7) shl 3) or (DestCode and 7)));
  362. end;
  363. procedure TASMInline.MovRegImm64(const Dest: TRegister64; const Value: UInt64);
  364. begin
  365. const DestCode = RegCode(Dest);
  366. WriteREX(True, False, False, DestCode >= 8);
  367. WriteByte(Byte($B8 + (DestCode and 7)));
  368. WriteUInt64(Value);
  369. end;
  370. procedure TASMInline.MovRegMemRSP(const Dest: TRegister64; const Disp: Integer);
  371. begin
  372. const DestCode = RegCode(Dest);
  373. WriteREX(True, DestCode >= 8, False, False);
  374. WriteByte($8B);
  375. WriteByte(Byte($84 or ((DestCode and 7) shl 3)));
  376. WriteByte($24);
  377. WriteInteger(Disp);
  378. end;
  379. procedure TASMInline.MovMemRSPReg(const Disp: Integer; const Src: TRegister64);
  380. begin
  381. const SrcCode = RegCode(Src);
  382. WriteREX(True, SrcCode >= 8, False, False);
  383. WriteByte($89);
  384. WriteByte(Byte($84 or ((SrcCode and 7) shl 3)));
  385. WriteByte($24);
  386. WriteInteger(Disp);
  387. end;
  388. procedure TASMInline.SubRsp(const Amount: Integer);
  389. begin
  390. WriteByte($48);
  391. WriteByte($81);
  392. WriteByte($EC);
  393. WriteInteger(Amount);
  394. end;
  395. procedure TASMInline.AddRsp(const Amount: Integer);
  396. begin
  397. WriteByte($48);
  398. WriteByte($81);
  399. WriteByte($C4);
  400. WriteInteger(Amount);
  401. end;
  402. procedure TASMInline.CallReg(const Reg: TRegister64);
  403. begin
  404. const RegValue = RegCode(Reg);
  405. WriteREX(False, False, False, RegValue >= 8);
  406. WriteByte($FF);
  407. WriteByte(Byte($D0 + (RegValue and 7)));
  408. end;
  409. procedure TASMInline.Ret;
  410. begin
  411. WriteByte($C3);
  412. end;
  413. {$ENDIF}
  414. function TASMInline.SaveAsMemory: Pointer;
  415. begin
  416. var Buf: Pointer;
  417. GetMem(Buf, Size);
  418. var OldProtect: Cardinal;
  419. VirtualProtect(Buf, SIZE_T(Size), PAGE_EXECUTE_READWRITE, OldProtect);
  420. {$IFDEF CPUX86}
  421. Relocate(Buf);
  422. {$ENDIF}
  423. Move(FBuffer.memory^, Buf^, Size);
  424. Result := Buf;
  425. end;
  426. function TASMInline.Size: Integer;
  427. begin
  428. if FBuffer.Size > High(Integer) then
  429. raise Exception.Create('Unexpected Size value');
  430. Result := Integer(FBuffer.Size);
  431. end;
  432. procedure TASMInline.WriteByte(const B: Byte);
  433. begin
  434. FBuffer.Write(B, SizeOf(B));
  435. end;
  436. procedure TASMInline.WriteInteger(const I: Integer);
  437. begin
  438. FBuffer.Write(I, SizeOf(I));
  439. end;
  440. {$IFNDEF CPUX86}
  441. procedure TASMInline.WriteUInt64(const U: UInt64);
  442. begin
  443. FBuffer.Write(U, SizeOf(U));
  444. end;
  445. {$ENDIF}
  446. end.