ASMInline.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. unit ASMInline;
  2. interface
  3. {$IFDEF CPUX86}
  4. {$WARN IMPLICIT_INTEGER_CAST_LOSS OFF}
  5. {$WARN IMPLICIT_CONVERSION_LOSS OFF}
  6. {ASM Inliner
  7. Nicholas Sherlock
  8. This is incomplete, I've only implemented enough to support InnoCallback.
  9. Instructions are stored in a TMemoryStream internally
  10. Instructions usually accept some combination of Registers, Immediates and
  11. Memory References. Memory References can either be of the simple form [EAX]
  12. (Where [EAX] is really a Delphi set), or the user can build the address with
  13. the TASMInline.addr() function. It'd be nice to have a function which builds
  14. the address from a string, too, allowing the more intuitive '[EAX+EBX*4+1000]'
  15. style.
  16. The generation of instruction-relative addresses generates Relocations, using
  17. SaveToMemory() automatically rebases using the relocations to make these correct
  18. in the final block of memory.
  19. !!!! Not all special cases have been implemented in WriteRegRef().
  20. Further reduced to just what's needed for Inno Setup by Martijn Laan
  21. }
  22. uses Sysutils, Windows, Classes, Contnrs;
  23. type
  24. TModMode = (mmNaked, mmDeref, mmDisp8, mmDisp32);
  25. TRegister32 = (EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI);
  26. TRegister32Set = set of TRegister32;
  27. TRegister16 = (AX, BX, CX, DX, SP, BP, SI, DI);
  28. TRegister16Set = set of TRegister16;
  29. TRegister8 = (AH, AL, BH, BL, CH, CL, DH, DL);
  30. TCLRegister = CL..CL;
  31. TMemSize = (ms8, ms16, ms32, ms64);
  32. TMemoryAddress = record
  33. size: TMemSize;
  34. usebase: boolean;
  35. base, index: TRegister32;
  36. offset: integer;
  37. scale: byte;
  38. end;
  39. EOperandSizeMismatch = class(exception)
  40. public
  41. constructor create;
  42. end;
  43. TRelocType = (rt32Bit);
  44. TReloc = class
  45. public
  46. position: longword;
  47. relocType: TRelocType;
  48. end;
  49. TASMInline = class
  50. private
  51. fbuffer: TMemoryStream;
  52. frelocs: TObjectList;
  53. fbase: longword;
  54. procedure AddRelocation(position: longword; relocType: TRelocType);
  55. function GetReloc(index: integer): TReloc;
  56. function RelocCount: integer;
  57. property Relocs[index: integer]: TReloc read GetReloc;
  58. procedure WriteByte(b: byte);
  59. procedure WriteInteger(i: integer);
  60. procedure WriteLongWord(l: longword);
  61. procedure WriteRegRef(reg: byte; base: TRegister32; deref: boolean; index: TRegister32; Offset: integer; Scale: byte; usebase: boolean); overload;
  62. procedure WriteRegRef(mem: TMemoryAddress; reg: TRegister32); overload;
  63. procedure WriteRegRef(reg: TRegister32; base: TRegister32; deref: boolean; index: TRegister32 = EAX; Offset: integer = 0; Scale: byte = 0; usebase: boolean = true); overload;
  64. public
  65. function Size: integer;
  66. procedure Relocate(base: pointer);
  67. function SaveAsMemory: pointer;
  68. procedure SaveToMemory(target: pointer);
  69. constructor create;
  70. destructor Destroy; override;
  71. function Addr(base: TRegister32; offset: Integer; size: TMemSize = ms32): TMemoryAddress; overload;
  72. //PUSH reg
  73. procedure Push(reg: TRegister32); overload;
  74. //POP reg
  75. procedure Pop(reg: TRegister32);
  76. //JUMP rel32
  77. procedure Jmp(target: pointer); overload;
  78. //MOV reg, imm
  79. procedure Mov(reg: TRegister32; b: longword); overload;
  80. //MOV reg, mem and MOV mem, reg
  81. procedure Mov(mem: TMemoryAddress; reg: TRegister32); overload;
  82. procedure Mov(reg: TRegister32; mem: TMemoryAddress); overload;
  83. end;
  84. {$ENDIF}
  85. implementation
  86. {$IFDEF CPUX86}
  87. constructor EOperandSizeMismatch.create;
  88. begin
  89. inherited create('Operand size mismatch');
  90. end;
  91. {Throw an exception if test<>match. Poor man's assert().
  92. Could overload to add other sorts of tests}
  93. procedure require(test: TMemSize; match: TMemSize);
  94. begin
  95. if test <> match then
  96. raise EOperandSizeMismatch.create;
  97. end;
  98. function regnum(reg: TRegister32): byte; overload;
  99. begin
  100. case reg of
  101. EAX: result := 0;
  102. EBX: result := 3;
  103. ECX: result := 1;
  104. EDX: result := 2;
  105. ESP: result := 4;
  106. EBP: result := 5;
  107. ESI: result := 6;
  108. EDI: result := 7;
  109. else raise exception.create('Unknown register...');
  110. end;
  111. end;
  112. function ModModeNum(m: TModMode): byte;
  113. begin
  114. case m of
  115. mmDeref: result := 0;
  116. mmDisp8: result := 1;
  117. mmDisp32: result := 2;
  118. mmNaked: result := 3;
  119. else raise exception.create('Invalid mod mode: ' + inttostr(ord(m)));
  120. end;
  121. end;
  122. function EncodeSIB(scale, index, base: byte): byte;
  123. begin
  124. result := byte(base or (index shl 3) or (scale shl 6));
  125. end;
  126. function EncodeModRM(aMod, aReg, aRM: byte): byte; overload;
  127. begin
  128. result := byte((aMod shl 6) or (areg shl 3) or aRM);
  129. end;
  130. {$IFOPT R+}
  131. {$DEFINE RESTORER}
  132. {$R-}
  133. {$ENDIF}
  134. {$IFOPT Q+}
  135. {$DEFINE RESTOREQ}
  136. {$Q-}
  137. {$ENDIF}
  138. procedure TASMInline.Relocate(base: pointer);
  139. var diff, orig: integer;
  140. i: integer;
  141. reloc: TReloc;
  142. begin
  143. const oldpos = fbuffer.Position;
  144. try
  145. diff := -(longword(base) - fbase);
  146. for i := 0 to RelocCount - 1 do begin
  147. reloc := Relocs[i];
  148. case reloc.relocType of
  149. rt32Bit: begin
  150. fbuffer.Seek(reloc.position, soBeginning);
  151. fbuffer.Read(orig, sizeof(orig));
  152. fbuffer.seek(-sizeof(orig), soCurrent);
  153. orig := LongWord(orig + diff);
  154. fbuffer.write(orig, sizeof(orig));
  155. end;
  156. end;
  157. end;
  158. fbase := longword(base);
  159. finally
  160. fbuffer.position := oldpos;
  161. end;
  162. end;
  163. {$IFDEF RESTORER}
  164. {$R+}
  165. {$ENDIF}
  166. {$IFDEF RESTOREQ}
  167. {Q+}
  168. {$ENDIF}
  169. function TASMInline.GetReloc(index: integer): TReloc;
  170. begin
  171. result := TReloc(frelocs[index]);
  172. end;
  173. function TASMInline.RelocCount: integer;
  174. begin
  175. result := frelocs.Count;
  176. end;
  177. procedure TASMInline.AddRelocation(position: longword; relocType: TRelocType);
  178. var reloc: TReloc;
  179. begin
  180. reloc := TReloc.Create;
  181. reloc.position := position;
  182. reloc.relocType := relocType;
  183. frelocs.add(reloc);
  184. end;
  185. function TASMInline.SaveAsMemory: pointer;
  186. var buf: pointer;
  187. oldprotect: Cardinal;
  188. begin
  189. GetMem(buf, size);
  190. VirtualProtect(buf, Size, PAGE_EXECUTE_READWRITE, oldprotect);
  191. SaveToMemory(buf);
  192. result := buf;
  193. end;
  194. procedure TASMInline.SaveToMemory(target: pointer);
  195. begin
  196. Relocate(target);
  197. Move(fbuffer.memory^, target^, size);
  198. end;
  199. function TASMInline.Addr(base: TRegister32; offset: Integer; size: TMemSize = ms32): TMemoryAddress;
  200. begin
  201. result.base := base;
  202. result.scale := 0; //don't use Index
  203. result.offset := offset;
  204. result.size := size;
  205. result.usebase := true;
  206. end;
  207. function TASMInline.Size: integer;
  208. begin
  209. result := fbuffer.size;
  210. end;
  211. procedure TASMInline.WriteInteger(i: integer);
  212. begin
  213. fbuffer.write(i, 4);
  214. end;
  215. procedure TASMInline.writelongword(l: longword);
  216. begin
  217. fbuffer.write(l, 4);
  218. end;
  219. procedure TASMInline.writebyte(b: byte);
  220. begin
  221. fbuffer.write(b, 1);
  222. end;
  223. procedure TASMInline.Pop(reg: TRegister32);
  224. begin
  225. writebyte($58 + regnum(reg));
  226. end;
  227. procedure TASMInline.Jmp(target: pointer);
  228. begin
  229. writebyte($E9);
  230. AddRelocation(fbuffer.position, rt32bit);
  231. WriteInteger(integer(target) - (integer(fBase) + fbuffer.Position + 4));
  232. end;
  233. procedure TASMInline.Push(reg: TRegister32);
  234. begin
  235. writebyte($50 + regnum(reg));
  236. end;
  237. procedure TASMInline.WriteRegRef(mem: TMemoryAddress; reg: TRegister32);
  238. begin
  239. writeregref(reg, mem.base, true, mem.index, mem.offset, mem.scale, mem.usebase);
  240. end;
  241. //Write the MODR/M and SIB byte for the given register or memory reference
  242. procedure TASMInline.WriteRegRef(reg: TRegister32; base: TRegister32; deref: boolean; index: TRegister32 = EAX; Offset: integer = 0; Scale: byte = 0; usebase: boolean = true);
  243. begin
  244. WriteRegRef(regnum(reg), base, deref, index, Offset, scale, usebase);
  245. end;
  246. procedure TASMInline.WriteRegRef(reg: byte; base: TRegister32; deref: boolean; index: TRegister32; Offset: integer; Scale: byte; usebase: boolean);
  247. type TOffSize = (osNone, os8, os32);
  248. var mode: TModMode;
  249. offsize: TOffSize;
  250. useSIB: boolean;
  251. areg, arm: Byte;
  252. begin
  253. if not deref then begin
  254. mode := mmNaked;
  255. offsize := osNone;
  256. end else
  257. if usebase = false then begin
  258. offsize := os32;
  259. mode := mmDeref;
  260. base := EBP; //the "no base" value
  261. end else
  262. if Offset = 0 then begin
  263. mode := mmDeref;
  264. offsize := osNone;
  265. end else
  266. if (offset >= -128) and (offset < 128) then begin //signed byte
  267. mode := mmDisp8;
  268. offsize := os8;
  269. end else begin
  270. mode := mmDisp32;
  271. offsize := os32;
  272. end;
  273. if (mode <> mmnaked) then begin
  274. usesib := (Scale > 0) or (base = ESP);
  275. end else usesib := false;
  276. if useSIB then begin //calculate scale, easiest just to use a case statement..
  277. case scale of
  278. 0: begin //dont want an index value
  279. index := ESP; //"none" value
  280. end;
  281. 1: scale := 0;
  282. 2: scale := 1;
  283. 4: scale := 2;
  284. 8: scale := 3;
  285. else raise exception.create('Invalid scale, valid values are 1,2,4,8.');
  286. end;
  287. end;
  288. if (not useSIB) and (mode = mmDeref) and (base = EBP) then begin
  289. //not available, use [EBP+0] instead
  290. mode := mmDisp8;
  291. offsize := os8;
  292. Offset := 0;
  293. end;
  294. arm := regnum(base);
  295. areg := reg;
  296. if usesib then
  297. WriteByte(EncodeModRM(ModModeNum(mode), areg, 4)) else
  298. WriteByte(EncodeModRM(ModModeNum(mode), areg, arm));
  299. if usesib then begin
  300. WriteByte(EncodeSIB(Scale, regnum(index), regnum(base)));
  301. end;
  302. //Do we have to append an offset?
  303. case offsize of
  304. os8: WriteByte(byte(offset)); //ignore sign..
  305. os32: writelongword(longword(offset));
  306. end;
  307. end;
  308. procedure TASMInline.Mov(mem: TMemoryAddress; reg: TRegister32);
  309. begin
  310. require(mem.size, ms32);
  311. WriteByte($89);
  312. WriteRegRef(mem, reg);
  313. end;
  314. procedure TASMInline.Mov(reg: TRegister32; mem: TMemoryAddress);
  315. begin
  316. require(mem.size, ms32);
  317. WriteByte($8B);
  318. WriteRegRef(mem, reg);
  319. end;
  320. procedure TASMInline.Mov(reg: TRegister32; b: longword);
  321. begin
  322. writebyte($B8 + regnum(reg));
  323. writelongword(b);
  324. end;
  325. constructor TASMInline.create;
  326. begin
  327. fbuffer := tmemorystream.create;
  328. frelocs := tobjectlist.create;
  329. end;
  330. destructor TASMInline.destroy;
  331. begin
  332. fbuffer.free;
  333. frelocs.free;
  334. inherited;
  335. end;
  336. {$ENDIF}
  337. end.