1 ----------------------------------------------------------------------------
2 -- LuaJIT module to save/list bytecode.
4 -- Copyright (C) 2005-2011 Mike Pall. All rights reserved.
5 -- Released under the MIT license. See Copyright Notice in luajit.h
6 ----------------------------------------------------------------------------
8 -- This module saves or lists the bytecode for an input file.
9 -- It's run by the -b command line option.
11 ------------------------------------------------------------------------------
13 local jit
= require("jit")
14 assert(jit
.version_num
== 20000, "LuaJIT core/library version mismatch")
16 -- Symbol name prefix for LuaJIT bytecode.
17 local LJBC_PREFIX
= "luaJIT_BC_"
19 ------------------------------------------------------------------------------
21 local function usage()
23 Save LuaJIT bytecode: luajit -b[options] input output
24 -l Only list bytecode.
25 -s Strip debug info (default).
27 -n name Set module name (default: auto-detect from input name).
28 -t type Set output file type (default: auto-detect from output name).
29 -a arch Override architecture for object files (default: native).
30 -o os Override OS for object files (default: native).
31 -e chunk Use chunk string as input.
32 -- Stop handling options.
33 - Use stdin as input and/or stdout as output.
35 File types: c h obj o raw (default)
40 local function check(ok
, ...)
41 if ok
then return ok
, ... end
42 io
.stderr
:write("luajit: ", ...)
47 local function readfile(input
)
48 if type(input
) == "function" then return input
end
49 if input
== "-" then input
= nil end
50 return check(loadfile(input
))
53 local function savefile(name
, mode
)
54 if name
== "-" then return io
.stdout
end
55 return check(io
.open(name
, mode
))
58 ------------------------------------------------------------------------------
61 raw
= "raw", c
= "c", h
= "h", o
= "obj", obj
= "obj",
65 x86
= true, x64
= true, arm
= true, ppc
= true, ppcspe
= true,
69 linux
= true, windows
= true, osx
= true, freebsd
= true, netbsd
= true,
70 openbsd
= true, solaris
= true,
73 local function checkarg(str
, map
, err
)
74 str
= string.lower(str
)
75 local s
= check(map
[str
], "unknown ", err
)
76 return s
== true and str
or s
79 local function detecttype(str
)
80 local ext
= string.match(string.lower(str
), "%.(%a+)$")
81 return map_type
[ext
] or "raw"
84 local function checkmodname(str
)
85 check(string.match(str
, "^[%w_.%-]+$"), "bad module name")
86 return string.gsub(str
, "[%.%-]", "_")
89 local function detectmodname(str
)
90 if type(str
) == "string" then
91 local tail
= string.match(str
, "[^/\\]+$")
92 if tail
then str
= tail
end
93 local head
= string.match(str
, "^(.*)%.[^.]*$")
94 if head
then str
= head
end
95 str
= string.match(str
, "^[%w_.%-]+")
99 check(str
, "cannot derive module name, use -n name")
100 return string.gsub(str
, "[%.%-]", "_")
103 ------------------------------------------------------------------------------
105 local function bcsave_tail(fp
, output
, s
)
106 local ok
, err
= fp
:write(s
)
107 if ok
and output
~= "-" then ok
, err
= fp
:close() end
108 check(ok
, "cannot write ", output
, ": ", err
)
111 local function bcsave_raw(output
, s
)
112 local fp
= savefile(output
, "wb")
113 bcsave_tail(fp
, output
, s
)
116 local function bcsave_c(ctx
, output
, s
)
117 local fp
= savefile(output
, "w")
118 if ctx
.type == "c" then
119 fp
:write(string.format([[
124 __declspec(dllexport)
126 const char %s%s[] = {
127 ]], LJBC_PREFIX
, ctx
.modname
))
129 fp
:write(string.format([[
131 static const char %s%s[] = {
132 ]], LJBC_PREFIX
, ctx
.modname
, #s
, LJBC_PREFIX
, ctx
.modname
))
134 local t
, n
, m
= {}, 0, 0
136 local b
= tostring(string.byte(s
, i
))
139 fp
:write(table.concat(t
, ",", 1, n
), ",\n")
145 bcsave_tail(fp
, output
, table.concat(t
, ",", 1, n
).."\n};\n")
148 local function bcsave_elfobj(ctx
, output
, s
, ffi
)
151 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
152 uint16_t type, machine;
154 uint32_t entry, phofs, shofs;
156 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
159 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
160 uint16_t type, machine;
162 uint64_t entry, phofs, shofs;
164 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
167 uint32_t name, type, flags, addr, ofs, size, link, info, align, entsize;
171 uint64_t flags, addr, ofs, size;
173 uint64_t align, entsize;
176 uint32_t name, value, size;
184 uint64_t value, size;
188 ELF32sectheader sect[6];
194 ELF64sectheader sect[6];
199 local symname
= LJBC_PREFIX
..ctx
.modname
200 local is64
, isbe
= false, false
201 if ctx
.arch
== "x64" then
203 elseif ctx
.arch
== "ppc" or ctx
.arch
== "ppcspe" then
207 -- Handle different host/target endianess.
208 local function f32(x
) return x
end
209 local f16
, fofs
= f32
, f32
210 if ffi
.abi("be") ~= isbe
then
212 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
214 function fofs(x
) return bit
.bswap(x
)*(2ll^
32) end
220 -- Create ELF object and fill in header.
221 local o
= ffi
.new(is64
and "ELF64obj" or "ELF32obj")
223 if ctx
.os
== "bsd" or ctx
.os
== "other" then -- Determine native hdr.eosabi.
224 local bf
= assert(io
.open("/bin/ls", "rb"))
225 local bs
= bf
:read(9)
228 check(hdr
.emagic
[0] == 127, "no support for writing native object files")
230 hdr
.emagic
= "\127ELF"
231 hdr
.eosabi
= ({ freebsd
=9, netbsd
=2, openbsd
=12, solaris
=6 })[ctx
.os
] or 0
233 hdr
.eclass
= is64
and 2 or 1
234 hdr
.eendian
= isbe
and 2 or 1
237 hdr
.machine
= f16(({ x86
=3, x64
=62, arm
=40, ppc
=20, ppcspe
=20 })[ctx
.arch
])
239 hdr
.shofs
= fofs(ffi
.offsetof(o
, "sect"))
240 hdr
.ehsize
= f16(ffi
.sizeof(hdr
))
241 hdr
.shentsize
= f16(ffi
.sizeof(o
.sect
[0]))
243 hdr
.shstridx
= f16(2)
245 -- Fill in sections and symbols.
246 local sofs
, ofs
= ffi
.offsetof(o
, "space"), 1
247 for i
,name
in ipairs
{
248 ".symtab", ".shstrtab", ".strtab", ".rodata", ".note.GNU-stack",
250 local sect
= o
.sect
[i
]
253 ffi
.copy(o
.space
+ofs
, name
)
256 o
.sect
[1].type = f32(2) -- .symtab
257 o
.sect
[1].link
= f32(3)
258 o
.sect
[1].info
= f32(1)
259 o
.sect
[1].align
= fofs(8)
260 o
.sect
[1].ofs
= fofs(ffi
.offsetof(o
, "sym"))
261 o
.sect
[1].entsize
= fofs(ffi
.sizeof(o
.sym
[0]))
262 o
.sect
[1].size
= fofs(ffi
.sizeof(o
.sym
))
263 o
.sym
[1].name
= f32(1)
264 o
.sym
[1].sectidx
= f16(4)
265 o
.sym
[1].size
= fofs(#s
)
267 o
.sect
[2].type = f32(3) -- .shstrtab
268 o
.sect
[2].ofs
= fofs(sofs
)
269 o
.sect
[2].size
= fofs(ofs
)
270 o
.sect
[3].type = f32(3) -- .strtab
271 o
.sect
[3].ofs
= fofs(sofs
+ ofs
)
272 o
.sect
[3].size
= fofs(#symname
+1)
273 ffi
.copy(o
.space
+ofs
+1, symname
)
274 ofs
= ofs
+ #symname
+ 2
275 o
.sect
[4].type = f32(1) -- .rodata
276 o
.sect
[4].flags
= fofs(2)
277 o
.sect
[4].ofs
= fofs(sofs
+ ofs
)
278 o
.sect
[4].size
= fofs(#s
)
279 o
.sect
[5].type = f32(1) -- .note.GNU-stack
280 o
.sect
[5].ofs
= fofs(sofs
+ ofs
+ #s
)
282 -- Write ELF object file.
283 local fp
= savefile(output
, "wb")
284 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
285 bcsave_tail(fp
, output
, s
)
288 local function bcsave_peobj(ctx
, output
, s
, ffi
)
291 uint16_t arch, nsects;
292 uint32_t time, symtabofs, nsyms;
293 uint16_t opthdrsz, flags;
297 uint32_t vsize, vaddr, size, ofs, relocofs, lineofs;
298 uint16_t nreloc, nline;
301 typedef struct __attribute((packed)) {
311 typedef struct __attribute((packed)) {
313 uint16_t nreloc, nline;
316 uint8_t comdatsel, unused[3];
321 // Must be an even number of symbol structs.
332 local symname
= LJBC_PREFIX
..ctx
.modname
334 if ctx
.arch
== "x86" then
335 symname
= "_"..symname
336 elseif ctx
.arch
== "x64" then
339 local symexport
= " /EXPORT:"..symname
..",DATA "
341 -- The file format is always little-endian. Swap if the host is big-endian.
342 local function f32(x
) return x
end
344 if ffi
.abi("be") then
346 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
349 -- Create PE object and fill in header.
350 local o
= ffi
.new("PEobj")
352 hdr
.arch
= f16(({ x86
=0x14c, x64
=0x8664, arm
=0x1c0, ppc
=0x1f2 })[ctx
.arch
])
354 hdr
.symtabofs
= f32(ffi
.offsetof(o
, "sym0"))
357 -- Fill in sections and symbols.
358 o
.sect
[0].name
= ".drectve"
359 o
.sect
[0].size
= f32(#symexport
)
360 o
.sect
[0].flags
= f32(0x00100a00)
363 o
.sym0
.name
= ".drectve"
365 o
.sym0aux
.size
= f32(#symexport
)
366 o
.sect
[1].name
= ".rdata"
367 o
.sect
[1].size
= f32(#s
)
368 o
.sect
[1].flags
= f32(0x40300040)
371 o
.sym1
.name
= ".rdata"
373 o
.sym1aux
.size
= f32(#s
)
376 o
.sym2
.nameref
[1] = f32(4)
377 o
.sym3
.sect
= f16(-1)
379 o
.sym3
.value
= f32(1)
380 o
.sym3
.name
= "@feat.00" -- Mark as SafeSEH compliant.
381 ffi
.copy(o
.space
, symname
)
382 local ofs
= #symname
+ 1
383 o
.strtabsize
= f32(ofs
+ 4)
384 o
.sect
[0].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
385 ffi
.copy(o
.space
+ ofs
, symexport
)
386 ofs
= ofs
+ #symexport
387 o
.sect
[1].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
389 -- Write PE object file.
390 local fp
= savefile(output
, "wb")
391 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
392 bcsave_tail(fp
, output
, s
)
395 local function bcsave_machobj(ctx
, output
, s
, ffi
)
396 check(false, "NYI: no support for writing OSX object files")
399 local function bcsave_obj(ctx
, output
, s
)
400 local ok
, ffi
= pcall(require
, "ffi")
401 check(ok
, "FFI library required to write this file type")
402 if ctx
.os
== "windows" then
403 return bcsave_peobj(ctx
, output
, s
, ffi
)
404 elseif ctx
.os
== "osx" then
405 return bcsave_machobj(ctx
, output
, s
, ffi
)
407 return bcsave_elfobj(ctx
, output
, s
, ffi
)
411 ------------------------------------------------------------------------------
413 local function bclist(input
, output
)
414 local f
= readfile(input
)
415 require("jit.bc").dump(f
, savefile(output
, "w"), true)
418 local function bcsave(ctx
, input
, output
)
419 local f
= readfile(input
)
420 local s
= string.dump(f
, ctx
.strip
)
423 t
= detecttype(output
)
427 bcsave_raw(output
, s
)
429 if not ctx
.modname
then ctx
.modname
= detectmodname(input
) end
431 bcsave_obj(ctx
, output
, s
)
433 bcsave_c(ctx
, output
, s
)
438 local function docmd(...)
443 strip
= true, arch
= jit
.arch
, os
= string.lower(jit
.os
),
444 type = false, modname
= false,
448 if type(a
) == "string" and string.sub(a
, 1, 1) == "-" and a
~= "-" then
450 if a
== "--" then break end
452 local opt
= string.sub(a
, m
, m
)
455 elseif opt
== "s" then
457 elseif opt
== "g" then
460 if arg
[n
] == nil or m
~= #a
then usage() end
462 if n
~= 1 then usage() end
463 arg
[1] = check(loadstring(arg
[1]))
464 elseif opt
== "n" then
465 ctx
.modname
= checkmodname(table.remove(arg
, n
))
466 elseif opt
== "t" then
467 ctx
.type = checkarg(table.remove(arg
, n
), map_type
, "file type")
468 elseif opt
== "a" then
469 ctx
.arch
= checkarg(table.remove(arg
, n
), map_arch
, "architecture")
470 elseif opt
== "o" then
471 ctx
.os
= checkarg(table.remove(arg
, n
), map_os
, "OS name")
482 if #arg
== 0 or #arg
> 2 then usage() end
483 bclist(arg
[1], arg
[2] or "-")
485 if #arg
~= 2 then usage() end
486 bcsave(ctx
, arg
[1], arg
[2])
490 ------------------------------------------------------------------------------
492 -- Public module functions.
495 start
= docmd
-- Process -b command line option.