diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/.gitignore | 1 | ||||
-rw-r--r-- | lib/bc.lua | 182 | ||||
-rw-r--r-- | lib/dis_x64.lua | 19 | ||||
-rw-r--r-- | lib/dis_x86.lua | 824 | ||||
-rw-r--r-- | lib/dump.lua | 567 | ||||
-rw-r--r-- | lib/v.lua | 156 |
6 files changed, 1749 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 00000000..500e2855 --- /dev/null +++ b/lib/.gitignore | |||
@@ -0,0 +1 @@ | |||
vmdef.lua | |||
diff --git a/lib/bc.lua b/lib/bc.lua new file mode 100644 index 00000000..532f2493 --- /dev/null +++ b/lib/bc.lua | |||
@@ -0,0 +1,182 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- LuaJIT bytecode listing module. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2009 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT/X license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- | ||
8 | -- This module lists the bytecode of a Lua function. If it's loaded by -jbc | ||
9 | -- it hooks into the parser and lists all functions of a chunk as they | ||
10 | -- are parsed. | ||
11 | -- | ||
12 | -- Example usage: | ||
13 | -- | ||
14 | -- luajit -jbc -e 'local x=0; for i=1,1e6 do x=x+i end; print(x)' | ||
15 | -- luajit -jbc=- foo.lua | ||
16 | -- luajit -jbc=foo.list foo.lua | ||
17 | -- | ||
18 | -- Default output is to stderr. To redirect the output to a file, pass a | ||
19 | -- filename as an argument (use '-' for stdout) or set the environment | ||
20 | -- variable LUAJIT_LISTFILE. The file is overwritten every time the module | ||
21 | -- is started. | ||
22 | -- | ||
23 | -- This module can also be used programmatically: | ||
24 | -- | ||
25 | -- local bc = require("jit.bc") | ||
26 | -- | ||
27 | -- local function foo() print("hello") end | ||
28 | -- | ||
29 | -- bc.dump(foo) --> -- BYTECODE -- [...] | ||
30 | -- print(bc.line(foo, 2)) --> 0002 KSTR 1 1 ; "hello" | ||
31 | -- | ||
32 | -- local out = { | ||
33 | -- -- Do something wich each line: | ||
34 | -- write = function(t, ...) io.write(...) end, | ||
35 | -- close = function(t) end, | ||
36 | -- flush = function(t) end, | ||
37 | -- } | ||
38 | -- bc.dump(foo, out) | ||
39 | -- | ||
40 | ------------------------------------------------------------------------------ | ||
41 | |||
42 | -- Cache some library functions and objects. | ||
43 | local jit = require("jit") | ||
44 | assert(jit.version_num == 20000, "LuaJIT core/library version mismatch") | ||
45 | local jutil = require("jit.util") | ||
46 | local vmdef = require("jit.vmdef") | ||
47 | local bit = require("bit") | ||
48 | local sub, gsub, format = string.sub, string.gsub, string.format | ||
49 | local byte, band, shr = string.byte, bit.band, bit.rshift | ||
50 | local funcinfo, funcbc, funck = jutil.funcinfo, jutil.funcbc, jutil.funck | ||
51 | local funcuvname = jutil.funcuvname | ||
52 | local bcnames = vmdef.bcnames | ||
53 | local stdout, stderr = io.stdout, io.stderr | ||
54 | |||
55 | ------------------------------------------------------------------------------ | ||
56 | |||
57 | local function ctlsub(c) | ||
58 | if c == "\n" then return "\\n" | ||
59 | elseif c == "\r" then return "\\r" | ||
60 | elseif c == "\t" then return "\\t" | ||
61 | elseif c == "\r" then return "\\r" | ||
62 | else return format("\\%03d", byte(c)) | ||
63 | end | ||
64 | end | ||
65 | |||
66 | -- Return one bytecode line. | ||
67 | local function bcline(func, pc, prefix) | ||
68 | local ins, m = funcbc(func, pc) | ||
69 | if not ins then return end | ||
70 | local ma, mb, mc = band(m, 7), band(m, 15*8), band(m, 15*128) | ||
71 | local a = band(shr(ins, 8), 0xff) | ||
72 | local oidx = 6*band(ins, 0xff) | ||
73 | local s = format("%04d %s %-6s %3s ", | ||
74 | pc, prefix or " ", sub(bcnames, oidx+1, oidx+6), ma == 0 and "" or a) | ||
75 | local d = shr(ins, 16) | ||
76 | if mc == 13*128 then -- BCMjump | ||
77 | if ma == 0 then | ||
78 | return format("%s=> %04d\n", sub(s, 1, -3), pc+d-0x7fff) | ||
79 | end | ||
80 | return format("%s=> %04d\n", s, pc+d-0x7fff) | ||
81 | end | ||
82 | if mb ~= 0 then d = band(d, 0xff) end | ||
83 | local kc | ||
84 | if mc == 10*128 then -- BCMstr | ||
85 | kc = funck(func, -d-1) | ||
86 | kc = format(#kc > 40 and '"%.40s"~' or '"%s"', gsub(kc, "%c", ctlsub)) | ||
87 | elseif mc == 9*128 then -- BCMnum | ||
88 | kc = funck(func, d) | ||
89 | elseif mc == 12*128 then -- BCMfunc | ||
90 | local fi = funcinfo(funck(func, -d-1)) | ||
91 | if fi.ffid then | ||
92 | kc = vmdef.ffnames[fi.ffid] | ||
93 | else | ||
94 | kc = fi.loc | ||
95 | end | ||
96 | elseif mc == 5*128 then -- BCMuv | ||
97 | kc = funcuvname(func, d) | ||
98 | end | ||
99 | if ma == 5 then -- BCMuv | ||
100 | local ka = funcuvname(func, a) | ||
101 | if kc then kc = ka.." ; "..kc else kc = ka end | ||
102 | end | ||
103 | if mb ~= 0 then | ||
104 | local b = shr(ins, 24) | ||
105 | if kc then return format("%s%3d %3d ; %s\n", s, b, d, kc) end | ||
106 | return format("%s%3d %3d\n", s, b, d) | ||
107 | end | ||
108 | if kc then return format("%s%3d ; %s\n", s, d, kc) end | ||
109 | if mc == 7*128 and d > 32767 then d = d - 65536 end -- BCMlits | ||
110 | return format("%s%3d\n", s, d) | ||
111 | end | ||
112 | |||
113 | -- Collect branch targets of a function. | ||
114 | local function bctargets(func) | ||
115 | local target = {} | ||
116 | for pc=1,1000000000 do | ||
117 | local ins, m = funcbc(func, pc) | ||
118 | if not ins then break end | ||
119 | if band(m, 15*128) == 13*128 then target[pc+shr(ins, 16)-0x7fff] = true end | ||
120 | end | ||
121 | return target | ||
122 | end | ||
123 | |||
124 | -- Dump bytecode instructions of a function. | ||
125 | local function bcdump(func, out) | ||
126 | if not out then out = stdout end | ||
127 | local fi = funcinfo(func) | ||
128 | out:write(format("-- BYTECODE -- %s-%d\n", fi.loc, fi.lastlinedefined)) | ||
129 | local target = bctargets(func) | ||
130 | for pc=1,1000000000 do | ||
131 | local s = bcline(func, pc, target[pc] and "=>") | ||
132 | if not s then break end | ||
133 | out:write(s) | ||
134 | end | ||
135 | out:write("\n") | ||
136 | out:flush() | ||
137 | end | ||
138 | |||
139 | ------------------------------------------------------------------------------ | ||
140 | |||
141 | -- Active flag and output file handle. | ||
142 | local active, out | ||
143 | |||
144 | -- List handler. | ||
145 | local function h_list(func) | ||
146 | return bcdump(func, out) | ||
147 | end | ||
148 | |||
149 | -- Detach list handler. | ||
150 | local function bclistoff() | ||
151 | if active then | ||
152 | active = false | ||
153 | jit.attach(h_list) | ||
154 | if out and out ~= stdout and out ~= stderr then out:close() end | ||
155 | out = nil | ||
156 | end | ||
157 | end | ||
158 | |||
159 | -- Open the output file and attach list handler. | ||
160 | local function bcliston(outfile) | ||
161 | if active then bclistoff() end | ||
162 | if not outfile then outfile = os.getenv("LUAJIT_LISTFILE") end | ||
163 | if outfile then | ||
164 | out = outfile == "-" and stdout or assert(io.open(outfile, "w")) | ||
165 | else | ||
166 | out = stderr | ||
167 | end | ||
168 | jit.attach(h_list, "bc") | ||
169 | active = true | ||
170 | end | ||
171 | |||
172 | -- Public module functions. | ||
173 | module(...) | ||
174 | |||
175 | line = bcline | ||
176 | dump = bcdump | ||
177 | targets = bctargets | ||
178 | |||
179 | on = bcliston | ||
180 | off = bclistoff | ||
181 | start = bcliston -- For -j command line option. | ||
182 | |||
diff --git a/lib/dis_x64.lua b/lib/dis_x64.lua new file mode 100644 index 00000000..da3d63f8 --- /dev/null +++ b/lib/dis_x64.lua | |||
@@ -0,0 +1,19 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- LuaJIT x64 disassembler wrapper module. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2009 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT/X license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- This module just exports the 64 bit functions from the combined | ||
8 | -- x86/x64 disassembler module. All the interesting stuff is there. | ||
9 | ------------------------------------------------------------------------------ | ||
10 | |||
11 | local require = require | ||
12 | |||
13 | module(...) | ||
14 | |||
15 | local dis_x86 = require(_PACKAGE.."dis_x86") | ||
16 | |||
17 | create = dis_x86.create64 | ||
18 | disass = dis_x86.disass64 | ||
19 | |||
diff --git a/lib/dis_x86.lua b/lib/dis_x86.lua new file mode 100644 index 00000000..8f127bee --- /dev/null +++ b/lib/dis_x86.lua | |||
@@ -0,0 +1,824 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- LuaJIT x86/x64 disassembler module. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2009 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT/X license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- This is a helper module used by the LuaJIT machine code dumper module. | ||
8 | -- | ||
9 | -- Sending small code snippets to an external disassembler and mixing the | ||
10 | -- output with our own stuff was too fragile. So I had to bite the bullet | ||
11 | -- and write yet another x86 disassembler. Oh well ... | ||
12 | -- | ||
13 | -- The output format is very similar to what ndisasm generates. But it has | ||
14 | -- been developed independently by looking at the opcode tables from the | ||
15 | -- Intel and AMD manuals. The supported instruction set is quite extensive | ||
16 | -- and reflects what a current generation Intel or AMD CPU implements in | ||
17 | -- 32 bit and 64 bit mode. Yes, this includes MMX, SSE, SSE2, SSE3, SSSE3, | ||
18 | -- SSE4.1, SSE4.2, SSE4a and even privileged and hypervisor (VMX/SVM) | ||
19 | -- instructions. | ||
20 | -- | ||
21 | -- Notes: | ||
22 | -- * The (useless) a16 prefix, 3DNow and pre-586 opcodes are unsupported. | ||
23 | -- * No attempt at optimization has been made -- it's fast enough for my needs. | ||
24 | -- * The public API may change when more architectures are added. | ||
25 | ------------------------------------------------------------------------------ | ||
26 | |||
27 | local type = type | ||
28 | local sub, byte, format = string.sub, string.byte, string.format | ||
29 | local match, gmatch, gsub = string.match, string.gmatch, string.gsub | ||
30 | local lower, rep = string.lower, string.rep | ||
31 | |||
32 | -- Map for 1st opcode byte in 32 bit mode. Ugly? Well ... read on. | ||
33 | local map_opc1_32 = { | ||
34 | --0x | ||
35 | [0]="addBmr","addVmr","addBrm","addVrm","addBai","addVai","push es","pop es", | ||
36 | "orBmr","orVmr","orBrm","orVrm","orBai","orVai","push cs","opc2*", | ||
37 | --1x | ||
38 | "adcBmr","adcVmr","adcBrm","adcVrm","adcBai","adcVai","push ss","pop ss", | ||
39 | "sbbBmr","sbbVmr","sbbBrm","sbbVrm","sbbBai","sbbVai","push ds","pop ds", | ||
40 | --2x | ||
41 | "andBmr","andVmr","andBrm","andVrm","andBai","andVai","es:seg","daa", | ||
42 | "subBmr","subVmr","subBrm","subVrm","subBai","subVai","cs:seg","das", | ||
43 | --3x | ||
44 | "xorBmr","xorVmr","xorBrm","xorVrm","xorBai","xorVai","ss:seg","aaa", | ||
45 | "cmpBmr","cmpVmr","cmpBrm","cmpVrm","cmpBai","cmpVai","ds:seg","aas", | ||
46 | --4x | ||
47 | "incVR","incVR","incVR","incVR","incVR","incVR","incVR","incVR", | ||
48 | "decVR","decVR","decVR","decVR","decVR","decVR","decVR","decVR", | ||
49 | --5x | ||
50 | "pushUR","pushUR","pushUR","pushUR","pushUR","pushUR","pushUR","pushUR", | ||
51 | "popUR","popUR","popUR","popUR","popUR","popUR","popUR","popUR", | ||
52 | --6x | ||
53 | "sz*pushaw,pusha","sz*popaw,popa","boundVrm","arplWmr", | ||
54 | "fs:seg","gs:seg","o16:","a16", | ||
55 | "pushUi","imulVrmi","pushBs","imulVrms", | ||
56 | "insb","insVS","outsb","outsVS", | ||
57 | --7x | ||
58 | "joBj","jnoBj","jbBj","jnbBj","jzBj","jnzBj","jbeBj","jaBj", | ||
59 | "jsBj","jnsBj","jpeBj","jpoBj","jlBj","jgeBj","jleBj","jgBj", | ||
60 | --8x | ||
61 | "arith!Bmi","arith!Vmi","arith!Bmi","arith!Vms", | ||
62 | "testBmr","testVmr","xchgBrm","xchgVrm", | ||
63 | "movBmr","movVmr","movBrm","movVrm", | ||
64 | "movVmg","leaVrm","movWgm","popUm", | ||
65 | --9x | ||
66 | "nop*xchgVaR|pause|xchgWaR|repne nop","xchgVaR","xchgVaR","xchgVaR", | ||
67 | "xchgVaR","xchgVaR","xchgVaR","xchgVaR", | ||
68 | "sz*cbw,cwde,cdqe","sz*cwd,cdq,cqo","call farViw","wait", | ||
69 | "sz*pushfw,pushf","sz*popfw,popf","sahf","lahf", | ||
70 | --Ax | ||
71 | "movBao","movVao","movBoa","movVoa", | ||
72 | "movsb","movsVS","cmpsb","cmpsVS", | ||
73 | "testBai","testVai","stosb","stosVS", | ||
74 | "lodsb","lodsVS","scasb","scasVS", | ||
75 | --Bx | ||
76 | "movBRi","movBRi","movBRi","movBRi","movBRi","movBRi","movBRi","movBRi", | ||
77 | "movVRI","movVRI","movVRI","movVRI","movVRI","movVRI","movVRI","movVRI", | ||
78 | --Cx | ||
79 | "shift!Bmu","shift!Vmu","retBw","ret","$lesVrm","$ldsVrm","movBmi","movVmi", | ||
80 | "enterBwu","leave","retfBw","retf","int3","intBu","into","iretVS", | ||
81 | --Dx | ||
82 | "shift!Bm1","shift!Vm1","shift!Bmc","shift!Vmc","aamBu","aadBu","salc","xlatb", | ||
83 | "fp*0","fp*1","fp*2","fp*3","fp*4","fp*5","fp*6","fp*7", | ||
84 | --Ex | ||
85 | "loopneBj","loopeBj","loopBj","sz*jcxzBj,jecxzBj,jrcxzBj", | ||
86 | "inBau","inVau","outBua","outVua", | ||
87 | "callVj","jmpVj","jmp farViw","jmpBj","inBad","inVad","outBda","outVda", | ||
88 | --Fx | ||
89 | "lock:","int1","repne:rep","rep:","hlt","cmc","testb!Bm","testv!Vm", | ||
90 | "clc","stc","cli","sti","cld","std","incb!Bm","incd!Vm", | ||
91 | } | ||
92 | assert(#map_opc1_32 == 255) | ||
93 | |||
94 | -- Map for 1st opcode byte in 64 bit mode (overrides only). | ||
95 | local map_opc1_64 = setmetatable({ | ||
96 | [0x06]=false, [0x07]=false, [0x0e]=false, | ||
97 | [0x16]=false, [0x17]=false, [0x1e]=false, [0x1f]=false, | ||
98 | [0x27]=false, [0x2f]=false, [0x37]=false, [0x3f]=false, | ||
99 | [0x60]=false, [0x61]=false, [0x62]=false, [0x63]="movsxdVrDmt", [0x67]="a32:", | ||
100 | [0x40]="rex*", [0x41]="rex*b", [0x42]="rex*x", [0x43]="rex*xb", | ||
101 | [0x44]="rex*r", [0x45]="rex*rb", [0x46]="rex*rx", [0x47]="rex*rxb", | ||
102 | [0x48]="rex*w", [0x49]="rex*wb", [0x4a]="rex*wx", [0x4b]="rex*wxb", | ||
103 | [0x4c]="rex*wr", [0x4d]="rex*wrb", [0x4e]="rex*wrx", [0x4f]="rex*wrxb", | ||
104 | [0x82]=false, [0x9a]=false, [0xc4]=false, [0xc5]=false, [0xce]=false, | ||
105 | [0xd4]=false, [0xd5]=false, [0xd6]=false, [0xea]=false, | ||
106 | }, { __index = map_opc1_32 }) | ||
107 | |||
108 | -- Map for 2nd opcode byte (0F xx). True CISC hell. Hey, I told you. | ||
109 | -- Prefix dependent MMX/SSE opcodes: (none)|rep|o16|repne, -|F3|66|F2 | ||
110 | local map_opc2 = { | ||
111 | --0x | ||
112 | [0]="sldt!Dmp","sgdt!Ump","larVrm","lslVrm",nil,"syscall","clts","sysret", | ||
113 | "invd","wbinvd",nil,"ud1",nil,"$prefetch!Bm","femms","3dnowMrmu", | ||
114 | --1x | ||
115 | "movupsXrm|movssXrm|movupdXrm|movsdXrm", | ||
116 | "movupsXmr|movssXmr|movupdXmr|movsdXmr", | ||
117 | "movhlpsXrm$movlpsXrm|movsldupXrm|movlpdXrm|movddupXrm", | ||
118 | "movlpsXmr||movlpdXmr", | ||
119 | "unpcklpsXrm||unpcklpdXrm", | ||
120 | "unpckhpsXrm||unpckhpdXrm", | ||
121 | "movlhpsXrm$movhpsXrm|movshdupXrm|movhpdXrm", | ||
122 | "movhpsXmr||movhpdXmr", | ||
123 | "$prefetcht!Bm","hintnopVm","hintnopVm","hintnopVm", | ||
124 | "hintnopVm","hintnopVm","hintnopVm","hintnopVm", | ||
125 | --2x | ||
126 | "movUmx$","movUmy$","movUxm$","movUym$","movUmz$",nil,"movUzm$",nil, | ||
127 | "movapsXrm||movapdXrm", | ||
128 | "movapsXmr||movapdXmr", | ||
129 | "cvtpi2psXrMm|cvtsi2ssXrVm|cvtpi2pdXrMm|cvtsi2sdXrVm", | ||
130 | "movntpsXmr|movntssXmr|movntpdXmr|movntsdXmr", | ||
131 | "cvttps2piMrXm|cvttss2siVrXm|cvttpd2piMrXm|cvttsd2siVrXm", | ||
132 | "cvtps2piMrXm|cvtss2siVrXm|cvtpd2piMrXm|cvtsd2siVrXm", | ||
133 | "ucomissXrm||ucomisdXrm", | ||
134 | "comissXrm||comisdXrm", | ||
135 | --3x | ||
136 | "wrmsr","rdtsc","rdmsr","rdpmc","sysenter","sysexit",nil,"getsec", | ||
137 | "opc3*38",nil,"opc3*3a",nil,nil,nil,nil,nil, | ||
138 | --4x | ||
139 | "cmovoVrm","cmovnoVrm","cmovbVrm","cmovnbVrm", | ||
140 | "cmovzVrm","cmovnzVrm","cmovbeVrm","cmovaVrm", | ||
141 | "cmovsVrm","cmovnsVrm","cmovpeVrm","cmovpoVrm", | ||
142 | "cmovlVrm","cmovgeVrm","cmovleVrm","cmovgVrm", | ||
143 | --5x | ||
144 | "movmskpsVrXm$||movmskpdVrXm$","sqrtpsXrm|sqrtssXrm|sqrtpdXrm|sqrtsdXrm", | ||
145 | "rsqrtpsXrm|rsqrtssXrm","rcppsXrm|rcpssXrm", | ||
146 | "andpsXrm||andpdXrm","andnpsXrm||andnpdXrm", | ||
147 | "orpsXrm||orpdXrm","xorpsXrm||xorpdXrm", | ||
148 | "addpsXrm|addssXrm|addpdXrm|addsdXrm","mulpsXrm|mulssXrm|mulpdXrm|mulsdXrm", | ||
149 | "cvtps2pdXrm|cvtss2sdXrm|cvtpd2psXrm|cvtsd2ssXrm", | ||
150 | "cvtdq2psXrm|cvttps2dqXrm|cvtps2dqXrm", | ||
151 | "subpsXrm|subssXrm|subpdXrm|subsdXrm","minpsXrm|minssXrm|minpdXrm|minsdXrm", | ||
152 | "divpsXrm|divssXrm|divpdXrm|divsdXrm","maxpsXrm|maxssXrm|maxpdXrm|maxsdXrm", | ||
153 | --6x | ||
154 | "punpcklbwPrm","punpcklwdPrm","punpckldqPrm","packsswbPrm", | ||
155 | "pcmpgtbPrm","pcmpgtwPrm","pcmpgtdPrm","packuswbPrm", | ||
156 | "punpckhbwPrm","punpckhwdPrm","punpckhdqPrm","packssdwPrm", | ||
157 | "||punpcklqdqXrm","||punpckhqdqXrm", | ||
158 | "movPrVSm","movqMrm|movdquXrm|movdqaXrm", | ||
159 | --7x | ||
160 | "pshufwMrmu|pshufhwXrmu|pshufdXrmu|pshuflwXrmu","pshiftw!Pmu", | ||
161 | "pshiftd!Pmu","pshiftq!Mmu||pshiftdq!Xmu", | ||
162 | "pcmpeqbPrm","pcmpeqwPrm","pcmpeqdPrm","emms|", | ||
163 | "vmreadUmr||extrqXmuu$|insertqXrmuu$","vmwriteUrm||extrqXrm$|insertqXrm$", | ||
164 | nil,nil, | ||
165 | "||haddpdXrm|haddpsXrm","||hsubpdXrm|hsubpsXrm", | ||
166 | "movVSmMr|movqXrm|movVSmXr","movqMmr|movdquXmr|movdqaXmr", | ||
167 | --8x | ||
168 | "joVj","jnoVj","jbVj","jnbVj","jzVj","jnzVj","jbeVj","jaVj", | ||
169 | "jsVj","jnsVj","jpeVj","jpoVj","jlVj","jgeVj","jleVj","jgVj", | ||
170 | --9x | ||
171 | "setoBm","setnoBm","setbBm","setnbBm","setzBm","setnzBm","setbeBm","setaBm", | ||
172 | "setsBm","setnsBm","setpeBm","setpoBm","setlBm","setgeBm","setleBm","setgBm", | ||
173 | --Ax | ||
174 | "push fs","pop fs","cpuid","btVmr","shldVmru","shldVmrc",nil,nil, | ||
175 | "push gs","pop gs","rsm","btsVmr","shrdVmru","shrdVmrc","fxsave!Dmp","imulVrm", | ||
176 | --Bx | ||
177 | "cmpxchgBmr","cmpxchgVmr","$lssVrm","btrVmr", | ||
178 | "$lfsVrm","$lgsVrm","movzxVrBmt","movzxVrWmt", | ||
179 | "|popcntVrm","ud2Dp","bt!Vmu","btcVmr", | ||
180 | "bsfVrm","bsrVrm|lzcntVrm|bsrWrm","movsxVrBmt","movsxVrWmt", | ||
181 | --Cx | ||
182 | "xaddBmr","xaddVmr", | ||
183 | "cmppsXrmu|cmpssXrmu|cmppdXrmu|cmpsdXrmu","$movntiVmr|", | ||
184 | "pinsrwPrWmu","pextrwDrPmu", | ||
185 | "shufpsXrmu||shufpdXrmu","$cmpxchg!Qmp", | ||
186 | "bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR","bswapVR", | ||
187 | --Dx | ||
188 | "||addsubpdXrm|addsubpsXrm","psrlwPrm","psrldPrm","psrlqPrm", | ||
189 | "paddqPrm","pmullwPrm", | ||
190 | "|movq2dqXrMm|movqXmr|movdq2qMrXm$","pmovmskbVrMm||pmovmskbVrXm", | ||
191 | "psubusbPrm","psubuswPrm","pminubPrm","pandPrm", | ||
192 | "paddusbPrm","padduswPrm","pmaxubPrm","pandnPrm", | ||
193 | --Ex | ||
194 | "pavgbPrm","psrawPrm","psradPrm","pavgwPrm", | ||
195 | "pmulhuwPrm","pmulhwPrm", | ||
196 | "|cvtdq2pdXrm|cvttpd2dqXrm|cvtpd2dqXrm","$movntqMmr||$movntdqXmr", | ||
197 | "psubsbPrm","psubswPrm","pminswPrm","porPrm", | ||
198 | "paddsbPrm","paddswPrm","pmaxswPrm","pxorPrm", | ||
199 | --Fx | ||
200 | "|||lddquXrm","psllwPrm","pslldPrm","psllqPrm", | ||
201 | "pmuludqPrm","pmaddwdPrm","psadbwPrm","maskmovqMrm||maskmovdquXrm$", | ||
202 | "psubbPrm","psubwPrm","psubdPrm","psubqPrm", | ||
203 | "paddbPrm","paddwPrm","padddPrm","ud", | ||
204 | } | ||
205 | assert(map_opc2[255] == "ud") | ||
206 | |||
207 | -- Map for three-byte opcodes. Can't wait for their next invention. | ||
208 | local map_opc3 = { | ||
209 | ["38"] = { -- [66] 0f 38 xx | ||
210 | --0x | ||
211 | [0]="pshufbPrm","phaddwPrm","phadddPrm","phaddswPrm", | ||
212 | "pmaddubswPrm","phsubwPrm","phsubdPrm","phsubswPrm", | ||
213 | "psignbPrm","psignwPrm","psigndPrm","pmulhrswPrm", | ||
214 | nil,nil,nil,nil, | ||
215 | --1x | ||
216 | "||pblendvbXrma",nil,nil,nil, | ||
217 | "||blendvpsXrma","||blendvpdXrma",nil,"||ptestXrm", | ||
218 | nil,nil,nil,nil, | ||
219 | "pabsbPrm","pabswPrm","pabsdPrm",nil, | ||
220 | --2x | ||
221 | "||pmovsxbwXrm","||pmovsxbdXrm","||pmovsxbqXrm","||pmovsxwdXrm", | ||
222 | "||pmovsxwqXrm","||pmovsxdqXrm",nil,nil, | ||
223 | "||pmuldqXrm","||pcmpeqqXrm","||$movntdqaXrm","||packusdwXrm", | ||
224 | nil,nil,nil,nil, | ||
225 | --3x | ||
226 | "||pmovzxbwXrm","||pmovzxbdXrm","||pmovzxbqXrm","||pmovzxwdXrm", | ||
227 | "||pmovzxwqXrm","||pmovzxdqXrm",nil,"||pcmpgtqXrm", | ||
228 | "||pminsbXrm","||pminsdXrm","||pminuwXrm","||pminudXrm", | ||
229 | "||pmaxsbXrm","||pmaxsdXrm","||pmaxuwXrm","||pmaxudXrm", | ||
230 | --4x | ||
231 | "||pmulddXrm","||phminposuwXrm", | ||
232 | --Fx | ||
233 | [0xf0] = "|||crc32TrBmt",[0xf1] = "|||crc32TrVmt", | ||
234 | }, | ||
235 | |||
236 | ["3a"] = { -- [66] 0f 3a xx | ||
237 | --0x | ||
238 | [0x00]=nil,nil,nil,nil,nil,nil,nil,nil, | ||
239 | "||roundpsXrmu","||roundpdXrmu","||roundssXrmu","||roundsdXrmu", | ||
240 | "||blendpsXrmu","||blendpdXrmu","||pblendwXrmu","palignrPrmu", | ||
241 | --1x | ||
242 | nil,nil,nil,nil, | ||
243 | "||pextrbVmXru","||pextrwVmXru","||pextrVmSXru","||extractpsVmXru", | ||
244 | nil,nil,nil,nil,nil,nil,nil,nil, | ||
245 | --2x | ||
246 | "||pinsrbXrVmu","||insertpsXrmu","||pinsrXrVmuS",nil, | ||
247 | --4x | ||
248 | [0x40] = "||dppsXrmu", | ||
249 | [0x41] = "||dppdXrmu", | ||
250 | [0x42] = "||mpsadbwXrmu", | ||
251 | --6x | ||
252 | [0x60] = "||pcmpestrmXrmu",[0x61] = "||pcmpestriXrmu", | ||
253 | [0x62] = "||pcmpistrmXrmu",[0x63] = "||pcmpistriXrmu", | ||
254 | }, | ||
255 | } | ||
256 | |||
257 | -- Map for VMX/SVM opcodes 0F 01 C0-FF (sgdt group with register operands). | ||
258 | local map_opcvm = { | ||
259 | [0xc1]="vmcall",[0xc2]="vmlaunch",[0xc3]="vmresume",[0xc4]="vmxoff", | ||
260 | [0xc8]="monitor",[0xc9]="mwait", | ||
261 | [0xd8]="vmrun",[0xd9]="vmmcall",[0xda]="vmload",[0xdb]="vmsave", | ||
262 | [0xdc]="stgi",[0xdd]="clgi",[0xde]="skinit",[0xdf]="invlpga", | ||
263 | [0xf8]="swapgs",[0xf9]="rdtscp", | ||
264 | } | ||
265 | |||
266 | -- Map for FP opcodes. And you thought stack machines are simple? | ||
267 | local map_opcfp = { | ||
268 | -- D8-DF 00-BF: opcodes with a memory operand. | ||
269 | -- D8 | ||
270 | [0]="faddFm","fmulFm","fcomFm","fcompFm","fsubFm","fsubrFm","fdivFm","fdivrFm", | ||
271 | "fldFm",nil,"fstFm","fstpFm","fldenvVm","fldcwWm","fnstenvVm","fnstcwWm", | ||
272 | -- DA | ||
273 | "fiaddDm","fimulDm","ficomDm","ficompDm", | ||
274 | "fisubDm","fisubrDm","fidivDm","fidivrDm", | ||
275 | -- DB | ||
276 | "fildDm","fisttpDm","fistDm","fistpDm",nil,"fld twordFmp",nil,"fstp twordFmp", | ||
277 | -- DC | ||
278 | "faddGm","fmulGm","fcomGm","fcompGm","fsubGm","fsubrGm","fdivGm","fdivrGm", | ||
279 | -- DD | ||
280 | "fldGm","fisttpQm","fstGm","fstpGm","frstorDmp",nil,"fnsaveDmp","fnstswWm", | ||
281 | -- DE | ||
282 | "fiaddWm","fimulWm","ficomWm","ficompWm", | ||
283 | "fisubWm","fisubrWm","fidivWm","fidivrWm", | ||
284 | -- DF | ||
285 | "fildWm","fisttpWm","fistWm","fistpWm", | ||
286 | "fbld twordFmp","fildQm","fbstp twordFmp","fistpQm", | ||
287 | -- xx C0-FF: opcodes with a pseudo-register operand. | ||
288 | -- D8 | ||
289 | "faddFf","fmulFf","fcomFf","fcompFf","fsubFf","fsubrFf","fdivFf","fdivrFf", | ||
290 | -- D9 | ||
291 | "fldFf","fxchFf",{"fnop"},nil, | ||
292 | {"fchs","fabs",nil,nil,"ftst","fxam"}, | ||
293 | {"fld1","fldl2t","fldl2e","fldpi","fldlg2","fldln2","fldz"}, | ||
294 | {"f2xm1","fyl2x","fptan","fpatan","fxtract","fprem1","fdecstp","fincstp"}, | ||
295 | {"fprem","fyl2xp1","fsqrt","fsincos","frndint","fscale","fsin","fcos"}, | ||
296 | -- DA | ||
297 | "fcmovbFf","fcmoveFf","fcmovbeFf","fcmovuFf",nil,{nil,"fucompp"},nil,nil, | ||
298 | -- DB | ||
299 | "fcmovnbFf","fcmovneFf","fcmovnbeFf","fcmovnuFf", | ||
300 | {nil,nil,"fnclex","fninit"},"fucomiFf","fcomiFf",nil, | ||
301 | -- DC | ||
302 | "fadd toFf","fmul toFf",nil,nil, | ||
303 | "fsub toFf","fsubr toFf","fdivr toFf","fdiv toFf", | ||
304 | -- DD | ||
305 | "ffreeFf",nil,"fstFf","fstpFf","fucomFf","fucompFf",nil,nil, | ||
306 | -- DE | ||
307 | "faddpFf","fmulpFf",nil,{nil,"fcompp"}, | ||
308 | "fsubrpFf","fsubpFf","fdivrpFf","fdivpFf", | ||
309 | -- DF | ||
310 | nil,nil,nil,nil,{"fnstsw ax"},"fucomipFf","fcomipFf",nil, | ||
311 | } | ||
312 | assert(map_opcfp[126] == "fcomipFf") | ||
313 | |||
314 | -- Map for opcode groups. The subkey is sp from the ModRM byte. | ||
315 | local map_opcgroup = { | ||
316 | arith = { "add", "or", "adc", "sbb", "and", "sub", "xor", "cmp" }, | ||
317 | shift = { "rol", "ror", "rcl", "rcr", "shl", "shr", "sal", "sar" }, | ||
318 | testb = { "testBmi", "testBmi", "not", "neg", "mul", "imul", "div", "idiv" }, | ||
319 | testv = { "testVmi", "testVmi", "not", "neg", "mul", "imul", "div", "idiv" }, | ||
320 | incb = { "inc", "dec" }, | ||
321 | incd = { "inc", "dec", "callDmp", "$call farDmp", | ||
322 | "jmpDmp", "$jmp farDmp", "pushUm" }, | ||
323 | sldt = { "sldt", "str", "lldt", "ltr", "verr", "verw" }, | ||
324 | sgdt = { "vm*$sgdt", "vm*$sidt", "$lgdt", "vm*$lidt", | ||
325 | "smsw", nil, "lmsw", "vm*$invlpg" }, | ||
326 | bt = { nil, nil, nil, nil, "bt", "bts", "btr", "btc" }, | ||
327 | cmpxchg = { nil, "sz*,cmpxchg8bQmp,cmpxchg16bXmp", nil, nil, | ||
328 | nil, nil, "vmptrld|vmxon|vmclear", "vmptrst" }, | ||
329 | pshiftw = { nil, nil, "psrlw", nil, "psraw", nil, "psllw" }, | ||
330 | pshiftd = { nil, nil, "psrld", nil, "psrad", nil, "pslld" }, | ||
331 | pshiftq = { nil, nil, "psrlq", nil, nil, nil, "psllq" }, | ||
332 | pshiftdq = { nil, nil, "psrlq", "psrldq", nil, nil, "psllq", "pslldq" }, | ||
333 | fxsave = { "$fxsave", "$fxrstor", "$ldmxcsr", "$stmxcsr", | ||
334 | nil, "lfenceDp$", "mfenceDp$", "sfenceDp$clflush" }, | ||
335 | prefetch = { "prefetch", "prefetchw" }, | ||
336 | prefetcht = { "prefetchnta", "prefetcht0", "prefetcht1", "prefetcht2" }, | ||
337 | } | ||
338 | |||
339 | ------------------------------------------------------------------------------ | ||
340 | |||
341 | -- Maps for register names. | ||
342 | local map_regs = { | ||
343 | B = { "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh", | ||
344 | "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" }, | ||
345 | B64 = { "al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", | ||
346 | "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" }, | ||
347 | W = { "ax", "cx", "dx", "bx", "sp", "bp", "si", "di", | ||
348 | "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" }, | ||
349 | D = { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", | ||
350 | "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" }, | ||
351 | Q = { "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", | ||
352 | "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" }, | ||
353 | M = { "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7", | ||
354 | "mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7" }, -- No x64 ext! | ||
355 | X = { "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", | ||
356 | "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15" }, | ||
357 | } | ||
358 | local map_segregs = { "es", "cs", "ss", "ds", "fs", "gs", "segr6", "segr7" } | ||
359 | |||
360 | -- Maps for size names. | ||
361 | local map_sz2n = { | ||
362 | B = 1, W = 2, D = 4, Q = 8, M = 8, X = 16, | ||
363 | } | ||
364 | local map_sz2prefix = { | ||
365 | B = "byte", W = "word", D = "dword", | ||
366 | Q = "qword", | ||
367 | M = "qword", X = "xword", | ||
368 | F = "dword", G = "qword", -- No need for sizes/register names for these two. | ||
369 | } | ||
370 | |||
371 | ------------------------------------------------------------------------------ | ||
372 | |||
373 | -- Output a nicely formatted line with an opcode and operands. | ||
374 | local function putop(ctx, text, operands) | ||
375 | local code, pos, hex = ctx.code, ctx.pos, "" | ||
376 | local hmax = ctx.hexdump | ||
377 | if hmax > 0 then | ||
378 | for i=ctx.start,pos-1 do | ||
379 | hex = hex..format("%02X", byte(code, i, i)) | ||
380 | end | ||
381 | if #hex > hmax then hex = sub(hex, 1, hmax)..". " | ||
382 | else hex = hex..rep(" ", hmax-#hex+2) end | ||
383 | end | ||
384 | if operands then text = text.." "..operands end | ||
385 | if ctx.o16 then text = "o16 "..text; ctx.o16 = false end | ||
386 | if ctx.a32 then text = "a32 "..text; ctx.a32 = false end | ||
387 | if ctx.rep then text = ctx.rep.." "..text; ctx.rep = false end | ||
388 | if ctx.rex then | ||
389 | local t = (ctx.rexw and "w" or "")..(ctx.rexr and "r" or "").. | ||
390 | (ctx.rexx and "x" or "")..(ctx.rexb and "b" or "") | ||
391 | if t ~= "" then text = "rex."..t.." "..text end | ||
392 | ctx.rexw = false; ctx.rexr = false; ctx.rexx = false; ctx.rexb = false | ||
393 | ctx.rex = false | ||
394 | end | ||
395 | if ctx.seg then | ||
396 | local text2, n = gsub(text, "%[", "["..ctx.seg..":") | ||
397 | if n == 0 then text = ctx.seg.." "..text else text = text2 end | ||
398 | ctx.seg = false | ||
399 | end | ||
400 | if ctx.lock then text = "lock "..text; ctx.lock = false end | ||
401 | local imm = ctx.imm | ||
402 | if imm then | ||
403 | local sym = ctx.symtab[imm] | ||
404 | if sym then text = text.."\t->"..sym end | ||
405 | end | ||
406 | ctx.out(format("%08x %s%s\n", ctx.addr+ctx.start, hex, text)) | ||
407 | ctx.mrm = false | ||
408 | ctx.start = pos | ||
409 | ctx.imm = nil | ||
410 | end | ||
411 | |||
412 | -- Clear all prefix flags. | ||
413 | local function clearprefixes(ctx) | ||
414 | ctx.o16 = false; ctx.seg = false; ctx.lock = false; ctx.rep = false | ||
415 | ctx.rexw = false; ctx.rexr = false; ctx.rexx = false; ctx.rexb = false | ||
416 | ctx.rex = false; ctx.a32 = false | ||
417 | end | ||
418 | |||
419 | -- Fallback for incomplete opcodes at the end. | ||
420 | local function incomplete(ctx) | ||
421 | ctx.pos = ctx.stop+1 | ||
422 | clearprefixes(ctx) | ||
423 | return putop(ctx, "(incomplete)") | ||
424 | end | ||
425 | |||
426 | -- Fallback for unknown opcodes. | ||
427 | local function unknown(ctx) | ||
428 | clearprefixes(ctx) | ||
429 | return putop(ctx, "(unknown)") | ||
430 | end | ||
431 | |||
432 | -- Return an immediate of the specified size. | ||
433 | local function getimm(ctx, pos, n) | ||
434 | if pos+n-1 > ctx.stop then return incomplete(ctx) end | ||
435 | local code = ctx.code | ||
436 | if n == 1 then | ||
437 | local b1 = byte(code, pos, pos) | ||
438 | return b1 | ||
439 | elseif n == 2 then | ||
440 | local b1, b2 = byte(code, pos, pos+1) | ||
441 | return b1+b2*256 | ||
442 | else | ||
443 | local b1, b2, b3, b4 = byte(code, pos, pos+3) | ||
444 | local imm = b1+b2*256+b3*65536+b4*16777216 | ||
445 | ctx.imm = imm | ||
446 | return imm | ||
447 | end | ||
448 | end | ||
449 | |||
450 | -- Process pattern string and generate the operands. | ||
451 | local function putpat(ctx, name, pat) | ||
452 | local operands, regs, sz, mode, sp, rm, sc, rx, sdisp | ||
453 | local code, pos, stop = ctx.code, ctx.pos, ctx.stop | ||
454 | |||
455 | -- Chars used: 1DFGIMPQRSTUVWXacdfgijmoprstuwxyz | ||
456 | for p in gmatch(pat, ".") do | ||
457 | local x = nil | ||
458 | if p == "V" or p == "U" then | ||
459 | if ctx.rexw then sz = "Q"; ctx.rexw = false | ||
460 | elseif ctx.o16 then sz = "W"; ctx.o16 = false | ||
461 | elseif p == "U" and ctx.x64 then sz = "Q" | ||
462 | else sz = "D" end | ||
463 | regs = map_regs[sz] | ||
464 | elseif p == "T" then | ||
465 | if ctx.rexw then sz = "Q"; ctx.rexw = false else sz = "D" end | ||
466 | regs = map_regs[sz] | ||
467 | elseif p == "B" then | ||
468 | sz = "B" | ||
469 | regs = ctx.rex and map_regs.B64 or map_regs.B | ||
470 | elseif match(p, "[WDQMXFG]") then | ||
471 | sz = p | ||
472 | regs = map_regs[sz] | ||
473 | elseif p == "P" then | ||
474 | sz = ctx.o16 and "X" or "M"; ctx.o16 = false | ||
475 | regs = map_regs[sz] | ||
476 | elseif p == "S" then | ||
477 | name = name..lower(sz) | ||
478 | elseif p == "s" then | ||
479 | local imm = getimm(ctx, pos, 1); if not imm then return end | ||
480 | x = imm <= 127 and format("+0x%02x", imm) | ||
481 | or format("-0x%02x", 256-imm) | ||
482 | pos = pos+1 | ||
483 | elseif p == "u" then | ||
484 | local imm = getimm(ctx, pos, 1); if not imm then return end | ||
485 | x = format("0x%02x", imm) | ||
486 | pos = pos+1 | ||
487 | elseif p == "w" then | ||
488 | local imm = getimm(ctx, pos, 2); if not imm then return end | ||
489 | x = format("0x%x", imm) | ||
490 | pos = pos+2 | ||
491 | elseif p == "o" then -- [offset] | ||
492 | if ctx.x64 then | ||
493 | local imm1 = getimm(ctx, pos, 4); if not imm1 then return end | ||
494 | local imm2 = getimm(ctx, pos+4, 4); if not imm2 then return end | ||
495 | x = format("[0x%08x%08x]", imm2, imm1) | ||
496 | pos = pos+8 | ||
497 | else | ||
498 | local imm = getimm(ctx, pos, 4); if not imm then return end | ||
499 | x = format("[0x%08x]", imm) | ||
500 | pos = pos+4 | ||
501 | end | ||
502 | elseif p == "i" or p == "I" then | ||
503 | local n = map_sz2n[sz] | ||
504 | if n == 8 and ctx.x64 and p == "I" then | ||
505 | local imm1 = getimm(ctx, pos, 4); if not imm1 then return end | ||
506 | local imm2 = getimm(ctx, pos+4, 4); if not imm2 then return end | ||
507 | x = format("0x%08x%08x", imm2, imm1) | ||
508 | else | ||
509 | if n == 8 then n = 4 end | ||
510 | local imm = getimm(ctx, pos, n); if not imm then return end | ||
511 | if sz == "Q" and (imm < 0 or imm > 0x7fffffff) then | ||
512 | imm = (0xffffffff+1)-imm | ||
513 | x = format(imm > 65535 and "-0x%08x" or "-0x%x", imm) | ||
514 | else | ||
515 | x = format(imm > 65535 and "0x%08x" or "0x%x", imm) | ||
516 | end | ||
517 | end | ||
518 | pos = pos+n | ||
519 | elseif p == "j" then | ||
520 | local n = map_sz2n[sz] | ||
521 | if n == 8 then n = 4 end | ||
522 | local imm = getimm(ctx, pos, n); if not imm then return end | ||
523 | if sz == "B" and imm > 127 then imm = imm-256 | ||
524 | elseif imm > 2147483647 then imm = imm-4294967296 end | ||
525 | pos = pos+n | ||
526 | imm = imm + pos + ctx.addr | ||
527 | if imm > 4294967295 and not ctx.x64 then imm = imm-4294967296 end | ||
528 | ctx.imm = imm | ||
529 | if sz == "W" then | ||
530 | x = format("word 0x%04x", imm%65536) | ||
531 | elseif ctx.x64 then | ||
532 | local lo = imm % 0x1000000 | ||
533 | x = format("0x%02x%06x", (imm-lo) / 0x1000000, lo) | ||
534 | else | ||
535 | x = format("0x%08x", imm) | ||
536 | end | ||
537 | elseif p == "R" then | ||
538 | local r = byte(code, pos-1, pos-1)%8 | ||
539 | if ctx.rexb then r = r + 8; ctx.rexb = false end | ||
540 | x = regs[r+1] | ||
541 | elseif p == "a" then x = regs[1] | ||
542 | elseif p == "c" then x = "cl" | ||
543 | elseif p == "d" then x = "dx" | ||
544 | elseif p == "1" then x = "1" | ||
545 | else | ||
546 | if not mode then | ||
547 | mode = ctx.mrm | ||
548 | if not mode then | ||
549 | if pos > stop then return incomplete(ctx) end | ||
550 | mode = byte(code, pos, pos) | ||
551 | pos = pos+1 | ||
552 | end | ||
553 | rm = mode%8; mode = (mode-rm)/8 | ||
554 | sp = mode%8; mode = (mode-sp)/8 | ||
555 | sdisp = "" | ||
556 | if mode < 3 then | ||
557 | if rm == 4 then | ||
558 | if pos > stop then return incomplete(ctx) end | ||
559 | sc = byte(code, pos, pos) | ||
560 | pos = pos+1 | ||
561 | rm = sc%8; sc = (sc-rm)/8 | ||
562 | rx = sc%8; sc = (sc-rx)/8 | ||
563 | if ctx.rexx then rx = rx + 8; ctx.rexx = false end | ||
564 | if rx == 4 then rx = nil end | ||
565 | end | ||
566 | if mode > 0 or rm == 5 then | ||
567 | local dsz = mode | ||
568 | if dsz ~= 1 then dsz = 4 end | ||
569 | local disp = getimm(ctx, pos, dsz); if not disp then return end | ||
570 | if mode == 0 then rm = nil end | ||
571 | if rm or rx or (not sc and ctx.x64 and not ctx.a32) then | ||
572 | if dsz == 1 and disp > 127 then | ||
573 | sdisp = format("-0x%x", 256-disp) | ||
574 | elseif disp >= 0 and disp <= 0x7fffffff then | ||
575 | sdisp = format("+0x%x", disp) | ||
576 | else | ||
577 | sdisp = format("-0x%x", (0xffffffff+1)-disp) | ||
578 | end | ||
579 | else | ||
580 | sdisp = format(ctx.x64 and not ctx.a32 and | ||
581 | not (disp >= 0 and disp <= 0x7fffffff) | ||
582 | and "0xffffffff%08x" or "0x%08x", disp) | ||
583 | end | ||
584 | pos = pos+dsz | ||
585 | end | ||
586 | end | ||
587 | if rm and ctx.rexb then rm = rm + 8; ctx.rexb = false end | ||
588 | if ctx.rexr then sp = sp + 8; ctx.rexr = false end | ||
589 | end | ||
590 | if p == "m" then | ||
591 | if mode == 3 then x = regs[rm+1] | ||
592 | else | ||
593 | local aregs = ctx.a32 and map_regs.D or ctx.aregs | ||
594 | local srm, srx = "", "" | ||
595 | if rm then srm = aregs[rm+1] | ||
596 | elseif not sc and ctx.x64 and not ctx.a32 then srm = "rip" end | ||
597 | ctx.a32 = false | ||
598 | if rx then | ||
599 | if rm then srm = srm.."+" end | ||
600 | srx = aregs[rx+1] | ||
601 | if sc > 0 then srx = srx.."*"..(2^sc) end | ||
602 | end | ||
603 | x = format("[%s%s%s]", srm, srx, sdisp) | ||
604 | end | ||
605 | if mode < 3 and | ||
606 | (not match(pat, "[aRrgp]") or match(pat, "t")) then -- Yuck. | ||
607 | x = map_sz2prefix[sz].." "..x | ||
608 | end | ||
609 | elseif p == "r" then x = regs[sp+1] | ||
610 | elseif p == "g" then x = map_segregs[sp+1] | ||
611 | elseif p == "p" then -- Suppress prefix. | ||
612 | elseif p == "f" then x = "st"..rm | ||
613 | elseif p == "x" then | ||
614 | if sp == 0 and ctx.lock and not ctx.x64 then | ||
615 | x = "CR8"; ctx.lock = false | ||
616 | else | ||
617 | x = "CR"..sp | ||
618 | end | ||
619 | elseif p == "y" then x = "DR"..sp | ||
620 | elseif p == "z" then x = "TR"..sp | ||
621 | elseif p == "t" then | ||
622 | else | ||
623 | error("bad pattern `"..pat.."'") | ||
624 | end | ||
625 | end | ||
626 | if x then operands = operands and operands..", "..x or x end | ||
627 | end | ||
628 | ctx.pos = pos | ||
629 | return putop(ctx, name, operands) | ||
630 | end | ||
631 | |||
632 | -- Forward declaration. | ||
633 | local map_act | ||
634 | |||
635 | -- Fetch and cache MRM byte. | ||
636 | local function getmrm(ctx) | ||
637 | local mrm = ctx.mrm | ||
638 | if not mrm then | ||
639 | local pos = ctx.pos | ||
640 | if pos > ctx.stop then return nil end | ||
641 | mrm = byte(ctx.code, pos, pos) | ||
642 | ctx.pos = pos+1 | ||
643 | ctx.mrm = mrm | ||
644 | end | ||
645 | return mrm | ||
646 | end | ||
647 | |||
648 | -- Dispatch to handler depending on pattern. | ||
649 | local function dispatch(ctx, opat, patgrp) | ||
650 | if not opat then return unknown(ctx) end | ||
651 | if match(opat, "%|") then -- MMX/SSE variants depending on prefix. | ||
652 | local p | ||
653 | if ctx.rep then | ||
654 | p = ctx.rep=="rep" and "%|([^%|]*)" or "%|[^%|]*%|[^%|]*%|([^%|]*)" | ||
655 | ctx.rep = false | ||
656 | elseif ctx.o16 then p = "%|[^%|]*%|([^%|]*)"; ctx.o16 = false | ||
657 | else p = "^[^%|]*" end | ||
658 | opat = match(opat, p) | ||
659 | if not opat then return unknown(ctx) end | ||
660 | -- ctx.rep = false; ctx.o16 = false | ||
661 | --XXX fails for 66 f2 0f 38 f1 06 crc32 eax,WORD PTR [esi] | ||
662 | --XXX remove in branches? | ||
663 | end | ||
664 | if match(opat, "%$") then -- reg$mem variants. | ||
665 | local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end | ||
666 | opat = match(opat, mrm >= 192 and "^[^%$]*" or "%$(.*)") | ||
667 | if opat == "" then return unknown(ctx) end | ||
668 | end | ||
669 | if opat == "" then return unknown(ctx) end | ||
670 | local name, pat = match(opat, "^([a-z0-9 ]*)(.*)") | ||
671 | if pat == "" and patgrp then pat = patgrp end | ||
672 | return map_act[sub(pat, 1, 1)](ctx, name, pat) | ||
673 | end | ||
674 | |||
675 | -- Get a pattern from an opcode map and dispatch to handler. | ||
676 | local function dispatchmap(ctx, opcmap) | ||
677 | local pos = ctx.pos | ||
678 | local opat = opcmap[byte(ctx.code, pos, pos)] | ||
679 | pos = pos + 1 | ||
680 | ctx.pos = pos | ||
681 | return dispatch(ctx, opat) | ||
682 | end | ||
683 | |||
684 | -- Map for action codes. The key is the first char after the name. | ||
685 | map_act = { | ||
686 | -- Simple opcodes without operands. | ||
687 | [""] = function(ctx, name, pat) | ||
688 | return putop(ctx, name) | ||
689 | end, | ||
690 | |||
691 | -- Operand size chars fall right through. | ||
692 | B = putpat, W = putpat, D = putpat, Q = putpat, | ||
693 | V = putpat, U = putpat, T = putpat, | ||
694 | M = putpat, X = putpat, P = putpat, | ||
695 | F = putpat, G = putpat, | ||
696 | |||
697 | -- Collect prefixes. | ||
698 | [":"] = function(ctx, name, pat) | ||
699 | ctx[pat == ":" and name or sub(pat, 2)] = name | ||
700 | if ctx.pos - ctx.start > 5 then return unknown(ctx) end -- Limit #prefixes. | ||
701 | end, | ||
702 | |||
703 | -- Chain to special handler specified by name. | ||
704 | ["*"] = function(ctx, name, pat) | ||
705 | return map_act[name](ctx, name, sub(pat, 2)) | ||
706 | end, | ||
707 | |||
708 | -- Use named subtable for opcode group. | ||
709 | ["!"] = function(ctx, name, pat) | ||
710 | local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end | ||
711 | return dispatch(ctx, map_opcgroup[name][((mrm-(mrm%8))/8)%8+1], sub(pat, 2)) | ||
712 | end, | ||
713 | |||
714 | -- o16,o32[,o64] variants. | ||
715 | sz = function(ctx, name, pat) | ||
716 | if ctx.o16 then ctx.o16 = false | ||
717 | else | ||
718 | pat = match(pat, ",(.*)") | ||
719 | if ctx.rexw then | ||
720 | local p = match(pat, ",(.*)") | ||
721 | if p then pat = p; ctx.rexw = false end | ||
722 | end | ||
723 | end | ||
724 | pat = match(pat, "^[^,]*") | ||
725 | return dispatch(ctx, pat) | ||
726 | end, | ||
727 | |||
728 | -- Two-byte opcode dispatch. | ||
729 | opc2 = function(ctx, name, pat) | ||
730 | return dispatchmap(ctx, map_opc2) | ||
731 | end, | ||
732 | |||
733 | -- Three-byte opcode dispatch. | ||
734 | opc3 = function(ctx, name, pat) | ||
735 | return dispatchmap(ctx, map_opc3[pat]) | ||
736 | end, | ||
737 | |||
738 | -- VMX/SVM dispatch. | ||
739 | vm = function(ctx, name, pat) | ||
740 | return dispatch(ctx, map_opcvm[ctx.mrm]) | ||
741 | end, | ||
742 | |||
743 | -- Floating point opcode dispatch. | ||
744 | fp = function(ctx, name, pat) | ||
745 | local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end | ||
746 | local rm = mrm%8 | ||
747 | local idx = pat*8 + ((mrm-rm)/8)%8 | ||
748 | if mrm >= 192 then idx = idx + 64 end | ||
749 | local opat = map_opcfp[idx] | ||
750 | if type(opat) == "table" then opat = opat[rm+1] end | ||
751 | return dispatch(ctx, opat) | ||
752 | end, | ||
753 | |||
754 | -- REX prefix. | ||
755 | rex = function(ctx, name, pat) | ||
756 | if ctx.rex then return unknown(ctx) end -- Only 1 REX prefix allowed. | ||
757 | for p in gmatch(pat, ".") do ctx["rex"..p] = true end | ||
758 | ctx.rex = true | ||
759 | end, | ||
760 | |||
761 | -- Special case for nop with REX prefix. | ||
762 | nop = function(ctx, name, pat) | ||
763 | return dispatch(ctx, ctx.rex and pat or "nop") | ||
764 | end, | ||
765 | } | ||
766 | |||
767 | ------------------------------------------------------------------------------ | ||
768 | |||
769 | -- Disassemble a block of code. | ||
770 | local function disass_block(ctx, ofs, len) | ||
771 | if not ofs then ofs = 0 end | ||
772 | local stop = len and ofs+len or #ctx.code | ||
773 | ofs = ofs + 1 | ||
774 | ctx.start = ofs | ||
775 | ctx.pos = ofs | ||
776 | ctx.stop = stop | ||
777 | ctx.imm = nil | ||
778 | ctx.mrm = false | ||
779 | clearprefixes(ctx) | ||
780 | while ctx.pos <= stop do dispatchmap(ctx, ctx.map1) end | ||
781 | if ctx.pos ~= ctx.start then incomplete(ctx) end | ||
782 | end | ||
783 | |||
784 | -- Extended API: create a disassembler context. Then call ctx:disass(ofs, len). | ||
785 | local function create_(code, addr, out) | ||
786 | local ctx = {} | ||
787 | ctx.code = code | ||
788 | ctx.addr = (addr or 0) - 1 | ||
789 | ctx.out = out or io.write | ||
790 | ctx.symtab = {} | ||
791 | ctx.disass = disass_block | ||
792 | ctx.hexdump = 16 | ||
793 | ctx.x64 = false | ||
794 | ctx.map1 = map_opc1_32 | ||
795 | ctx.aregs = map_regs.D | ||
796 | return ctx | ||
797 | end | ||
798 | |||
799 | local function create64_(code, addr, out) | ||
800 | local ctx = create_(code, addr, out) | ||
801 | ctx.x64 = true | ||
802 | ctx.map1 = map_opc1_64 | ||
803 | ctx.aregs = map_regs.Q | ||
804 | return ctx | ||
805 | end | ||
806 | |||
807 | -- Simple API: disassemble code (a string) at address and output via out. | ||
808 | local function disass_(code, addr, out) | ||
809 | create_(code, addr, out):disass() | ||
810 | end | ||
811 | |||
812 | local function disass64_(code, addr, out) | ||
813 | create64_(code, addr, out):disass() | ||
814 | end | ||
815 | |||
816 | |||
817 | -- Public module functions. | ||
818 | module(...) | ||
819 | |||
820 | create = create_ | ||
821 | create64 = create64_ | ||
822 | disass = disass_ | ||
823 | disass64 = disass64_ | ||
824 | |||
diff --git a/lib/dump.lua b/lib/dump.lua new file mode 100644 index 00000000..9fde87c1 --- /dev/null +++ b/lib/dump.lua | |||
@@ -0,0 +1,567 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- LuaJIT compiler dump module. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2009 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT/X license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- | ||
8 | -- This module can be used to debug the JIT compiler itself. It dumps the | ||
9 | -- code representations and structures used in various compiler stages. | ||
10 | -- | ||
11 | -- Example usage: | ||
12 | -- | ||
13 | -- luajit -jdump -e "local x=0; for i=1,1e6 do x=x+i end; print(x)" | ||
14 | -- luajit -jdump=im -e "for i=1,1000 do for j=1,1000 do end end" | less -R | ||
15 | -- luajit -jdump=is myapp.lua | less -R | ||
16 | -- luajit -jdump=-b myapp.lua | ||
17 | -- luajit -jdump=+aH,myapp.html myapp.lua | ||
18 | -- luajit -jdump=ixT,myapp.dump myapp.lua | ||
19 | -- | ||
20 | -- The first argument specifies the dump mode. The second argument gives | ||
21 | -- the output file name. Default output is to stdout, unless the environment | ||
22 | -- variable LUAJIT_DUMPFILE is set. The file is overwritten every time the | ||
23 | -- module is started. | ||
24 | -- | ||
25 | -- Different features can be turned on or off with the dump mode. If the | ||
26 | -- mode starts with a '+', the following features are added to the default | ||
27 | -- set of features; a '-' removes them. Otherwise the features are replaced. | ||
28 | -- | ||
29 | -- The following dump features are available (* marks the default): | ||
30 | -- | ||
31 | -- * t Print a line for each started, ended or aborted trace (see also -jv). | ||
32 | -- * b Dump the traced bytecode. | ||
33 | -- * i Dump the IR (intermediate representation). | ||
34 | -- r Augment the IR with register/stack slots. | ||
35 | -- s Dump the snapshot map. | ||
36 | -- * m Dump the generated machine code. | ||
37 | -- x Print each taken trace exit. | ||
38 | -- X Print each taken trace exit and the contents of all registers. | ||
39 | -- | ||
40 | -- The output format can be set with the following characters: | ||
41 | -- | ||
42 | -- T Plain text output. | ||
43 | -- A ANSI-colored text output | ||
44 | -- H Colorized HTML + CSS output. | ||
45 | -- | ||
46 | -- The default output format is plain text. It's set to ANSI-colored text | ||
47 | -- if the COLORTERM variable is set. Note: this is independent of any output | ||
48 | -- redirection, which is actually considered a feature. | ||
49 | -- | ||
50 | -- You probably want to use less -R to enjoy viewing ANSI-colored text from | ||
51 | -- a pipe or a file. Add this to your ~/.bashrc: export LESS="-R" | ||
52 | -- | ||
53 | ------------------------------------------------------------------------------ | ||
54 | |||
55 | -- Cache some library functions and objects. | ||
56 | local jit = require("jit") | ||
57 | assert(jit.version_num == 20000, "LuaJIT core/library version mismatch") | ||
58 | local jutil = require("jit.util") | ||
59 | local vmdef = require("jit.vmdef") | ||
60 | local funcinfo, funcbc = jutil.funcinfo, jutil.funcbc | ||
61 | local traceinfo, traceir, tracek = jutil.traceinfo, jutil.traceir, jutil.tracek | ||
62 | local tracemc, traceexitstub = jutil.tracemc, jutil.traceexitstub | ||
63 | local tracesnap = jutil.tracesnap | ||
64 | local bit = require("bit") | ||
65 | local band, shl, shr = bit.band, bit.lshift, bit.rshift | ||
66 | local sub, gsub, format = string.sub, string.gsub, string.format | ||
67 | local byte, char, rep = string.byte, string.char, string.rep | ||
68 | local type, tostring = type, tostring | ||
69 | local stdout, stderr = io.stdout, io.stderr | ||
70 | |||
71 | -- Load other modules on-demand. | ||
72 | local bcline, discreate | ||
73 | |||
74 | -- Active flag, output file handle and dump mode. | ||
75 | local active, out, dumpmode | ||
76 | |||
77 | ------------------------------------------------------------------------------ | ||
78 | |||
79 | local symtab = {} | ||
80 | local nexitsym = 0 | ||
81 | |||
82 | -- Fill symbol table with trace exit addresses. | ||
83 | local function fillsymtab(nexit) | ||
84 | local t = symtab | ||
85 | if nexit > nexitsym then | ||
86 | for i=nexitsym,nexit-1 do t[traceexitstub(i)] = tostring(i) end | ||
87 | nexitsym = nexit | ||
88 | end | ||
89 | return t | ||
90 | end | ||
91 | |||
92 | local function dumpwrite(s) | ||
93 | out:write(s) | ||
94 | end | ||
95 | |||
96 | -- Disassemble machine code. | ||
97 | local function dump_mcode(tr) | ||
98 | local info = traceinfo(tr) | ||
99 | if not info then return end | ||
100 | local mcode, addr, loop = tracemc(tr) | ||
101 | if not mcode then return end | ||
102 | if not discreate then | ||
103 | discreate = require("jit.dis_"..jit.arch).create | ||
104 | end | ||
105 | out:write("---- TRACE ", tr, " mcode ", #mcode, "\n") | ||
106 | local ctx = discreate(mcode, addr, dumpwrite) | ||
107 | ctx.hexdump = 0 | ||
108 | ctx.symtab = fillsymtab(info.nexit) | ||
109 | if loop ~= 0 then | ||
110 | symtab[addr+loop] = "LOOP" | ||
111 | ctx:disass(0, loop) | ||
112 | out:write("->LOOP:\n") | ||
113 | ctx:disass(loop, #mcode-loop) | ||
114 | symtab[addr+loop] = nil | ||
115 | else | ||
116 | ctx:disass(0, #mcode) | ||
117 | end | ||
118 | end | ||
119 | |||
120 | ------------------------------------------------------------------------------ | ||
121 | |||
122 | local irtype_text = { | ||
123 | [0] = "nil", | ||
124 | "fal", | ||
125 | "tru", | ||
126 | "lud", | ||
127 | "str", | ||
128 | "ptr", | ||
129 | "thr", | ||
130 | "pro", | ||
131 | "fun", | ||
132 | "t09", | ||
133 | "tab", | ||
134 | "udt", | ||
135 | "num", | ||
136 | "int", | ||
137 | "i8 ", | ||
138 | "u8 ", | ||
139 | "i16", | ||
140 | "u16", | ||
141 | } | ||
142 | |||
143 | local colortype_ansi = { | ||
144 | [0] = "%s", | ||
145 | "%s", | ||
146 | "%s", | ||
147 | "%s", | ||
148 | "\027[32m%s\027[m", | ||
149 | "%s", | ||
150 | "\027[1m%s\027[m", | ||
151 | "%s", | ||
152 | "\027[1m%s\027[m", | ||
153 | "%s", | ||
154 | "\027[31m%s\027[m", | ||
155 | "\027[36m%s\027[m", | ||
156 | "\027[34m%s\027[m", | ||
157 | "\027[35m%s\027[m", | ||
158 | "\027[35m%s\027[m", | ||
159 | "\027[35m%s\027[m", | ||
160 | "\027[35m%s\027[m", | ||
161 | "\027[35m%s\027[m", | ||
162 | } | ||
163 | |||
164 | local function colorize_text(s, t) | ||
165 | return s | ||
166 | end | ||
167 | |||
168 | local function colorize_ansi(s, t) | ||
169 | return format(colortype_ansi[t], s) | ||
170 | end | ||
171 | |||
172 | local irtype_ansi = setmetatable({}, | ||
173 | { __index = function(tab, t) | ||
174 | local s = colorize_ansi(irtype_text[t], t); tab[t] = s; return s; end }) | ||
175 | |||
176 | local html_escape = { ["<"] = "<", [">"] = ">", ["&"] = "&", } | ||
177 | |||
178 | local function colorize_html(s, t) | ||
179 | s = gsub(s, "[<>&]", html_escape) | ||
180 | return format('<span class="irt_%s">%s</span>', irtype_text[t], s) | ||
181 | end | ||
182 | |||
183 | local irtype_html = setmetatable({}, | ||
184 | { __index = function(tab, t) | ||
185 | local s = colorize_html(irtype_text[t], t); tab[t] = s; return s; end }) | ||
186 | |||
187 | local header_html = [[ | ||
188 | <style type="text/css"> | ||
189 | background { background: #ffffff; color: #000000; } | ||
190 | pre.ljdump { | ||
191 | font-size: 10pt; | ||
192 | background: #f0f4ff; | ||
193 | color: #000000; | ||
194 | border: 1px solid #bfcfff; | ||
195 | padding: 0.5em; | ||
196 | margin-left: 2em; | ||
197 | margin-right: 2em; | ||
198 | } | ||
199 | span.irt_str { color: #00a000; } | ||
200 | span.irt_thr, span.irt_fun { color: #404040; font-weight: bold; } | ||
201 | span.irt_tab { color: #c00000; } | ||
202 | span.irt_udt { color: #00c0c0; } | ||
203 | span.irt_num { color: #0000c0; } | ||
204 | span.irt_int { color: #c000c0; } | ||
205 | </style> | ||
206 | ]] | ||
207 | |||
208 | local colorize, irtype | ||
209 | |||
210 | -- Lookup table to convert some literals into names. | ||
211 | local litname = { | ||
212 | ["SLOAD "] = { [0] = "", "I", "R", "RI", "P", "PI", "PR", "PRI", }, | ||
213 | ["XLOAD "] = { [0] = "", "unaligned", }, | ||
214 | ["TOINT "] = { [0] = "check", "index", "", }, | ||
215 | ["FLOAD "] = vmdef.irfield, | ||
216 | ["FREF "] = vmdef.irfield, | ||
217 | ["FPMATH"] = vmdef.irfpm, | ||
218 | } | ||
219 | |||
220 | local function ctlsub(c) | ||
221 | if c == "\n" then return "\\n" | ||
222 | elseif c == "\r" then return "\\r" | ||
223 | elseif c == "\t" then return "\\t" | ||
224 | elseif c == "\r" then return "\\r" | ||
225 | else return format("\\%03d", byte(c)) | ||
226 | end | ||
227 | end | ||
228 | |||
229 | local function formatk(tr, idx) | ||
230 | local k, t, slot = tracek(tr, idx) | ||
231 | local tn = type(k) | ||
232 | local s | ||
233 | if tn == "number" then | ||
234 | if k == 2^52+2^51 then | ||
235 | s = "bias" | ||
236 | else | ||
237 | s = format("%+.14g", k) | ||
238 | end | ||
239 | elseif tn == "string" then | ||
240 | s = format(#k > 20 and '"%.20s"~' or '"%s"', gsub(k, "%c", ctlsub)) | ||
241 | elseif tn == "function" then | ||
242 | local fi = funcinfo(k) | ||
243 | if fi.ffid then | ||
244 | s = vmdef.ffnames[fi.ffid] | ||
245 | else | ||
246 | s = fi.loc | ||
247 | end | ||
248 | elseif tn == "table" then | ||
249 | s = format("{%p}", k) | ||
250 | elseif tn == "userdata" then | ||
251 | if t == 11 then | ||
252 | s = format("userdata:%p", k) | ||
253 | else | ||
254 | s = format("[%p]", k) | ||
255 | if s == "[0x00000000]" then s = "NULL" end | ||
256 | end | ||
257 | else | ||
258 | s = tostring(k) -- For primitives. | ||
259 | end | ||
260 | s = colorize(format("%-4s", s), t) | ||
261 | if slot then | ||
262 | s = format("%s @%d", s, slot) | ||
263 | end | ||
264 | return s | ||
265 | end | ||
266 | |||
267 | local function printsnap(tr, snap) | ||
268 | for i=1,#snap do | ||
269 | local ref = snap[i] | ||
270 | if not ref then | ||
271 | out:write("---- ") | ||
272 | elseif ref < 0 then | ||
273 | out:write(formatk(tr, ref), " ") | ||
274 | else | ||
275 | local m, ot, op1, op2 = traceir(tr, ref) | ||
276 | local t = band(ot, 15) | ||
277 | local sep = " " | ||
278 | if t == 8 then | ||
279 | local oidx = 6*shr(ot, 8) | ||
280 | local op = sub(vmdef.irnames, oidx+1, oidx+6) | ||
281 | if op == "FRAME " then | ||
282 | sep = "|" | ||
283 | end | ||
284 | end | ||
285 | out:write(colorize(format("%04d", ref), t), sep) | ||
286 | end | ||
287 | end | ||
288 | out:write("]\n") | ||
289 | end | ||
290 | |||
291 | -- Dump snapshots (not interleaved with IR). | ||
292 | local function dump_snap(tr) | ||
293 | out:write("---- TRACE ", tr, " snapshots\n") | ||
294 | for i=0,1000000000 do | ||
295 | local snap = tracesnap(tr, i) | ||
296 | if not snap then break end | ||
297 | out:write(format("#%-3d %04d [ ", i, snap[0])) | ||
298 | printsnap(tr, snap) | ||
299 | end | ||
300 | end | ||
301 | |||
302 | -- NYI: should really get the register map from the disassembler. | ||
303 | local reg_map = { | ||
304 | [0] = "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", | ||
305 | "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", | ||
306 | } | ||
307 | |||
308 | -- Return a register name or stack slot for a rid/sp location. | ||
309 | local function ridsp_name(ridsp) | ||
310 | local rid = band(ridsp, 0xff) | ||
311 | if ridsp > 255 then return format("[%x]", shr(ridsp, 8)*4) end | ||
312 | if rid < 128 then return reg_map[rid] end | ||
313 | return "" | ||
314 | end | ||
315 | |||
316 | -- Dump IR and interleaved snapshots. | ||
317 | local function dump_ir(tr, dumpsnap, dumpreg) | ||
318 | local info = traceinfo(tr) | ||
319 | if not info then return end | ||
320 | local nins = info.nins | ||
321 | out:write("---- TRACE ", tr, " IR\n") | ||
322 | local irnames = vmdef.irnames | ||
323 | local snapref = 65536 | ||
324 | local snap, snapno | ||
325 | if dumpsnap then | ||
326 | snap = tracesnap(tr, 0) | ||
327 | snapref = snap[0] | ||
328 | snapno = 0 | ||
329 | end | ||
330 | for ins=1,nins do | ||
331 | if ins >= snapref then | ||
332 | if dumpreg then | ||
333 | out:write(format(".... SNAP #%-3d [ ", snapno)) | ||
334 | else | ||
335 | out:write(format(".... SNAP #%-3d [ ", snapno)) | ||
336 | end | ||
337 | printsnap(tr, snap) | ||
338 | snapno = snapno + 1 | ||
339 | snap = tracesnap(tr, snapno) | ||
340 | snapref = snap and snap[0] or 65536 | ||
341 | end | ||
342 | local m, ot, op1, op2, ridsp = traceir(tr, ins) | ||
343 | local oidx, t = 6*shr(ot, 8), band(ot, 31) | ||
344 | local op = sub(irnames, oidx+1, oidx+6) | ||
345 | if op == "LOOP " then | ||
346 | if dumpreg then | ||
347 | out:write(format("%04d ------------ LOOP ------------\n", ins)) | ||
348 | else | ||
349 | out:write(format("%04d ------ LOOP ------------\n", ins)) | ||
350 | end | ||
351 | elseif op ~= "NOP " and (dumpreg or op ~= "RENAME") then | ||
352 | if dumpreg then | ||
353 | out:write(format("%04d %-5s ", ins, ridsp_name(ridsp))) | ||
354 | else | ||
355 | out:write(format("%04d ", ins)) | ||
356 | end | ||
357 | out:write(format("%s%s %s %s ", | ||
358 | band(ot, 64) == 0 and " " or ">", | ||
359 | band(ot, 128) == 0 and " " or "+", | ||
360 | irtype[t], op)) | ||
361 | local m1 = band(m, 3) | ||
362 | if m1 ~= 3 then -- op1 != IRMnone | ||
363 | if op1 < 0 then | ||
364 | out:write(formatk(tr, op1)) | ||
365 | else | ||
366 | out:write(format(m1 == 0 and "%04d" or "#%-3d", op1)) | ||
367 | end | ||
368 | local m2 = band(m, 3*4) | ||
369 | if m2 ~= 3*4 then -- op2 != IRMnone | ||
370 | if m2 == 1*4 then -- op2 == IRMlit | ||
371 | local litn = litname[op] | ||
372 | if litn and litn[op2] then | ||
373 | out:write(" ", litn[op2]) | ||
374 | else | ||
375 | out:write(format(" #%-3d", op2)) | ||
376 | end | ||
377 | elseif op2 < 0 then | ||
378 | out:write(" ", formatk(tr, op2)) | ||
379 | else | ||
380 | out:write(format(" %04d", op2)) | ||
381 | end | ||
382 | end | ||
383 | end | ||
384 | out:write("\n") | ||
385 | end | ||
386 | end | ||
387 | if snap then | ||
388 | if dumpreg then | ||
389 | out:write(format(".... SNAP #%-3d [ ", snapno)) | ||
390 | else | ||
391 | out:write(format(".... SNAP #%-3d [ ", snapno)) | ||
392 | end | ||
393 | printsnap(tr, snap) | ||
394 | end | ||
395 | end | ||
396 | |||
397 | ------------------------------------------------------------------------------ | ||
398 | |||
399 | local recprefix = "" | ||
400 | local recdepth = 0 | ||
401 | |||
402 | -- Format trace error message. | ||
403 | local function fmterr(err, info) | ||
404 | if type(err) == "number" then | ||
405 | if type(info) == "function" then | ||
406 | local fi = funcinfo(info) | ||
407 | if fi.ffid then | ||
408 | info = vmdef.ffnames[fi.ffid] | ||
409 | else | ||
410 | info = fi.loc | ||
411 | end | ||
412 | end | ||
413 | err = format(vmdef.traceerr[err], info) | ||
414 | end | ||
415 | return err | ||
416 | end | ||
417 | |||
418 | -- Dump trace states. | ||
419 | local function dump_trace(what, tr, func, pc, otr, oex) | ||
420 | if what == "stop" or (what == "abort" and dumpmode.a) then | ||
421 | if dumpmode.i then dump_ir(tr, dumpmode.s, dumpmode.r and what == "stop") | ||
422 | elseif dumpmode.s then dump_snap(tr) end | ||
423 | if dumpmode.m then dump_mcode(tr) end | ||
424 | end | ||
425 | if what == "start" then | ||
426 | if dumpmode.H then out:write('<pre class="ljdump">\n') end | ||
427 | out:write("---- TRACE ", tr, " ", what) | ||
428 | if otr then out:write(" ", otr, "/", oex) end | ||
429 | local fi = funcinfo(func, pc) | ||
430 | out:write(" ", fi.loc, "\n") | ||
431 | recprefix = "" | ||
432 | reclevel = 0 | ||
433 | elseif what == "stop" or what == "abort" then | ||
434 | out:write("---- TRACE ", tr, " ", what) | ||
435 | recprefix = nil | ||
436 | if what == "abort" then | ||
437 | local fi = funcinfo(func, pc) | ||
438 | out:write(" ", fi.loc, " -- ", fmterr(otr, oex), "\n") | ||
439 | else | ||
440 | local link = traceinfo(tr).link | ||
441 | if link == tr then | ||
442 | link = "loop" | ||
443 | elseif link == 0 then | ||
444 | link = "interpreter" | ||
445 | end | ||
446 | out:write(" -> ", link, "\n") | ||
447 | end | ||
448 | if dumpmode.H then out:write("</pre>\n\n") else out:write("\n") end | ||
449 | else | ||
450 | out:write("---- TRACE ", what, "\n\n") | ||
451 | end | ||
452 | out:flush() | ||
453 | end | ||
454 | |||
455 | -- Dump recorded bytecode. | ||
456 | local function dump_record(tr, func, pc, depth, callee) | ||
457 | if depth ~= recdepth then | ||
458 | recdepth = depth | ||
459 | recprefix = rep(" .", depth) | ||
460 | end | ||
461 | local line = bcline(func, pc, recprefix) | ||
462 | if dumpmode.H then line = gsub(line, "[<>&]", html_escape) end | ||
463 | if type(callee) == "function" then | ||
464 | local fi = funcinfo(callee) | ||
465 | if fi.ffid then | ||
466 | out:write(sub(line, 1, -2), " ; ", vmdef.ffnames[fi.ffid], "\n") | ||
467 | else | ||
468 | out:write(sub(line, 1, -2), " ; ", fi.loc, "\n") | ||
469 | end | ||
470 | else | ||
471 | out:write(line) | ||
472 | end | ||
473 | if band(funcbc(func, pc), 0xff) < 16 then -- Write JMP for cond. ORDER BC | ||
474 | out:write(bcline(func, pc+1, recprefix)) | ||
475 | end | ||
476 | end | ||
477 | |||
478 | ------------------------------------------------------------------------------ | ||
479 | |||
480 | -- Dump taken trace exits. | ||
481 | local function dump_texit(tr, ex, ngpr, nfpr, ...) | ||
482 | out:write("---- TRACE ", tr, " exit ", ex, "\n") | ||
483 | if dumpmode.X then | ||
484 | local regs = {...} | ||
485 | for i=1,ngpr do | ||
486 | out:write(format(" %08x", regs[i])) | ||
487 | if i % 8 == 0 then out:write("\n") end | ||
488 | end | ||
489 | for i=1,nfpr do | ||
490 | out:write(format(" %+17.14g", regs[ngpr+i])) | ||
491 | if i % 4 == 0 then out:write("\n") end | ||
492 | end | ||
493 | end | ||
494 | end | ||
495 | |||
496 | ------------------------------------------------------------------------------ | ||
497 | |||
498 | -- Detach dump handlers. | ||
499 | local function dumpoff() | ||
500 | if active then | ||
501 | active = false | ||
502 | jit.attach(dump_texit) | ||
503 | jit.attach(dump_record) | ||
504 | jit.attach(dump_trace) | ||
505 | if out and out ~= stdout and out ~= stderr then out:close() end | ||
506 | out = nil | ||
507 | end | ||
508 | end | ||
509 | |||
510 | -- Open the output file and attach dump handlers. | ||
511 | local function dumpon(opt, outfile) | ||
512 | if active then dumpoff() end | ||
513 | |||
514 | local colormode = os.getenv("COLORTERM") and "A" or "T" | ||
515 | if opt then | ||
516 | opt = gsub(opt, "[TAH]", function(mode) colormode = mode; return ""; end) | ||
517 | end | ||
518 | |||
519 | local m = { t=true, b=true, i=true, m=true, } | ||
520 | if opt and opt ~= "" then | ||
521 | local o = sub(opt, 1, 1) | ||
522 | if o ~= "+" and o ~= "-" then m = {} end | ||
523 | for i=1,#opt do m[sub(opt, i, i)] = (o ~= "-") end | ||
524 | end | ||
525 | dumpmode = m | ||
526 | |||
527 | if m.t or m.b or m.i or m.s or m.m then | ||
528 | jit.attach(dump_trace, "trace") | ||
529 | end | ||
530 | if m.b then | ||
531 | jit.attach(dump_record, "record") | ||
532 | if not bcline then bcline = require("jit.bc").line end | ||
533 | end | ||
534 | if m.x or m.X then | ||
535 | jit.attach(dump_texit, "texit") | ||
536 | end | ||
537 | |||
538 | if not outfile then outfile = os.getenv("LUAJIT_DUMPFILE") end | ||
539 | if outfile then | ||
540 | out = outfile == "-" and stdout or assert(io.open(outfile, "w")) | ||
541 | else | ||
542 | out = stdout | ||
543 | end | ||
544 | |||
545 | m[colormode] = true | ||
546 | if colormode == "A" then | ||
547 | colorize = colorize_ansi | ||
548 | irtype = irtype_ansi | ||
549 | elseif colormode == "H" then | ||
550 | colorize = colorize_html | ||
551 | irtype = irtype_html | ||
552 | out:write(header_html) | ||
553 | else | ||
554 | colorize = colorize_text | ||
555 | irtype = irtype_text | ||
556 | end | ||
557 | |||
558 | active = true | ||
559 | end | ||
560 | |||
561 | -- Public module functions. | ||
562 | module(...) | ||
563 | |||
564 | on = dumpon | ||
565 | off = dumpoff | ||
566 | start = dumpon -- For -j command line option. | ||
567 | |||
diff --git a/lib/v.lua b/lib/v.lua new file mode 100644 index 00000000..39fb8ed5 --- /dev/null +++ b/lib/v.lua | |||
@@ -0,0 +1,156 @@ | |||
1 | ---------------------------------------------------------------------------- | ||
2 | -- Verbose mode of the LuaJIT compiler. | ||
3 | -- | ||
4 | -- Copyright (C) 2005-2009 Mike Pall. All rights reserved. | ||
5 | -- Released under the MIT/X license. See Copyright Notice in luajit.h | ||
6 | ---------------------------------------------------------------------------- | ||
7 | -- | ||
8 | -- This module shows verbose information about the progress of the | ||
9 | -- JIT compiler. It prints one line for each generated trace. This module | ||
10 | -- is useful to see which code has been compiled or where the compiler | ||
11 | -- punts and falls back to the interpreter. | ||
12 | -- | ||
13 | -- Example usage: | ||
14 | -- | ||
15 | -- luajit -jv -e "for i=1,1000 do for j=1,1000 do end end" | ||
16 | -- luajit -jv=myapp.out myapp.lua | ||
17 | -- | ||
18 | -- Default output is to stderr. To redirect the output to a file, pass a | ||
19 | -- filename as an argument (use '-' for stdout) or set the environment | ||
20 | -- variable LUAJIT_VERBOSEFILE. The file is overwritten every time the | ||
21 | -- module is started. | ||
22 | -- | ||
23 | -- The output from the first example should look like this: | ||
24 | -- | ||
25 | -- [TRACE 1 (command line):1] | ||
26 | -- [TRACE 2 (1/3) (command line):1 -> 1] | ||
27 | -- | ||
28 | -- The first number in each line is the internal trace number. Next are | ||
29 | -- the file name ('(command line)') and the line number (':1') where the | ||
30 | -- trace has started. Side traces also show the parent trace number and | ||
31 | -- the exit number where they are attached to in parentheses ('(1/3)'). | ||
32 | -- An arrow at the end shows where the trace links to ('-> 1'), unless | ||
33 | -- it loops to itself. | ||
34 | -- | ||
35 | -- In this case the inner loop gets hot and is traced first, generating | ||
36 | -- a root trace. Then the last exit from the 1st trace gets hot, too, | ||
37 | -- and triggers generation of the 2nd trace. The side trace follows the | ||
38 | -- path along the outer loop and *around* the inner loop, back to its | ||
39 | -- start, and then links to the 1st trace. Yes, this may seem unusual, | ||
40 | -- if you know how traditional compilers work. Trace compilers are full | ||
41 | -- of surprises like this -- have fun! :-) | ||
42 | -- | ||
43 | -- Aborted traces are shown like this: | ||
44 | -- | ||
45 | -- [TRACE --- foo.lua:44 -- leaving loop in root trace at foo:lua:50] | ||
46 | -- | ||
47 | -- Don't worry -- trace aborts are quite common, even in programs which | ||
48 | -- can be fully compiled. The compiler may retry several times until it | ||
49 | -- finds a suitable trace. | ||
50 | -- | ||
51 | -- Of course this doesn't work with features that are not-yet-implemented | ||
52 | -- (NYI error messages). The VM simply falls back to the interpreter. This | ||
53 | -- may not matter at all if the particular trace is not very high up in | ||
54 | -- the CPU usage profile. Oh, and the interpreter is quite fast, too. | ||
55 | -- | ||
56 | -- Also check out the -jdump module, which prints all the gory details. | ||
57 | -- | ||
58 | ------------------------------------------------------------------------------ | ||
59 | |||
60 | -- Cache some library functions and objects. | ||
61 | local jit = require("jit") | ||
62 | assert(jit.version_num == 20000, "LuaJIT core/library version mismatch") | ||
63 | local jutil = require("jit.util") | ||
64 | local vmdef = require("jit.vmdef") | ||
65 | local funcinfo, traceinfo = jutil.funcinfo, jutil.traceinfo | ||
66 | local type, format = type, string.format | ||
67 | local stdout, stderr = io.stdout, io.stderr | ||
68 | |||
69 | -- Active flag and output file handle. | ||
70 | local active, out | ||
71 | |||
72 | ------------------------------------------------------------------------------ | ||
73 | |||
74 | local startloc, startex | ||
75 | |||
76 | -- Format trace error message. | ||
77 | local function fmterr(err, info) | ||
78 | if type(err) == "number" then | ||
79 | if type(info) == "function" then | ||
80 | local fi = funcinfo(info) | ||
81 | if fi.ffid then | ||
82 | info = vmdef.ffnames[fi.ffid] | ||
83 | else | ||
84 | info = fi.loc | ||
85 | end | ||
86 | end | ||
87 | err = format(vmdef.traceerr[err], info) | ||
88 | end | ||
89 | return err | ||
90 | end | ||
91 | |||
92 | -- Dump trace states. | ||
93 | local function dump_trace(what, tr, func, pc, otr, oex) | ||
94 | if what == "start" then | ||
95 | startloc = funcinfo(func, pc).loc | ||
96 | startex = otr and "("..otr.."/"..oex..") " or "" | ||
97 | else | ||
98 | if what == "abort" then | ||
99 | local loc = funcinfo(func, pc).loc | ||
100 | if loc ~= startloc then | ||
101 | out:write(format("[TRACE --- %s%s -- %s at %s]\n", | ||
102 | startex, startloc, fmterr(otr, oex), loc)) | ||
103 | else | ||
104 | out:write(format("[TRACE --- %s%s -- %s]\n", | ||
105 | startex, startloc, fmterr(otr, oex))) | ||
106 | end | ||
107 | elseif what == "stop" then | ||
108 | local link = traceinfo(tr).link | ||
109 | if link == 0 then | ||
110 | out:write(format("[TRACE %3s %s%s -- fallback to interpreter]\n", | ||
111 | tr, startex, startloc)) | ||
112 | elseif link == tr then | ||
113 | out:write(format("[TRACE %3s %s%s]\n", tr, startex, startloc)) | ||
114 | else | ||
115 | out:write(format("[TRACE %3s %s%s -> %d]\n", | ||
116 | tr, startex, startloc, link)) | ||
117 | end | ||
118 | else | ||
119 | out:write(format("[TRACE %s]\n", what)) | ||
120 | end | ||
121 | out:flush() | ||
122 | end | ||
123 | end | ||
124 | |||
125 | ------------------------------------------------------------------------------ | ||
126 | |||
127 | -- Detach dump handlers. | ||
128 | local function dumpoff() | ||
129 | if active then | ||
130 | active = false | ||
131 | jit.attach(dump_trace) | ||
132 | if out and out ~= stdout and out ~= stderr then out:close() end | ||
133 | out = nil | ||
134 | end | ||
135 | end | ||
136 | |||
137 | -- Open the output file and attach dump handlers. | ||
138 | local function dumpon(outfile) | ||
139 | if active then dumpoff() end | ||
140 | if not outfile then outfile = os.getenv("LUAJIT_VERBOSEFILE") end | ||
141 | if outfile then | ||
142 | out = outfile == "-" and stdout or assert(io.open(outfile, "w")) | ||
143 | else | ||
144 | out = stderr | ||
145 | end | ||
146 | jit.attach(dump_trace, "trace") | ||
147 | active = true | ||
148 | end | ||
149 | |||
150 | -- Public module functions. | ||
151 | module(...) | ||
152 | |||
153 | on = dumpon | ||
154 | off = dumpoff | ||
155 | start = dumpon -- For -j command line option. | ||
156 | |||