1 ----------------------------------------------------------------------------
2 -- LuaJIT module to save/list bytecode.
4 -- Copyright (C) 2005-2016 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
== 20100, "LuaJIT core/library version mismatch")
15 local bit
= require("bit")
17 -- Symbol name prefix for LuaJIT bytecode.
18 local LJBC_PREFIX
= "luaJIT_BC_"
20 ------------------------------------------------------------------------------
22 local function usage()
24 Save LuaJIT bytecode: luajit -b[options] input output
25 -l Only list bytecode.
26 -s Strip debug info (default).
28 -n name Set module name (default: auto-detect from input name).
29 -t type Set output file type (default: auto-detect from output name).
30 -a arch Override architecture for object files (default: native).
31 -o os Override OS for object files (default: native).
32 -e chunk Use chunk string as input.
33 -- Stop handling options.
34 - Use stdin as input and/or stdout as output.
36 File types: c h obj o raw (default)
41 local function check(ok
, ...)
42 if ok
then return ok
, ... end
43 io
.stderr
:write("luajit: ", ...)
48 local function readfile(input
)
49 if type(input
) == "function" then return input
end
50 if input
== "-" then input
= nil end
51 return check(loadfile(input
))
54 local function savefile(name
, mode
)
55 if name
== "-" then return io
.stdout
end
56 return check(io
.open(name
, mode
))
59 ------------------------------------------------------------------------------
62 raw
= "raw", c
= "c", h
= "h", o
= "obj", obj
= "obj",
66 x86
= true, x64
= true, arm
= true, arm64
= true, ppc
= true,
67 mips
= true, mipsel
= true,
71 linux
= true, windows
= true, osx
= true, freebsd
= true, netbsd
= true,
72 openbsd
= true, dragonfly
= true, solaris
= true,
75 local function checkarg(str
, map
, err
)
76 str
= string.lower(str
)
77 local s
= check(map
[str
], "unknown ", err
)
78 return s
== true and str
or s
81 local function detecttype(str
)
82 local ext
= string.match(string.lower(str
), "%.(%a+)$")
83 return map_type
[ext
] or "raw"
86 local function checkmodname(str
)
87 check(string.match(str
, "^[%w_.%-]+$"), "bad module name")
88 return string.gsub(str
, "[%.%-]", "_")
91 local function detectmodname(str
)
92 if type(str
) == "string" then
93 local tail
= string.match(str
, "[^/\\]+$")
94 if tail
then str
= tail
end
95 local head
= string.match(str
, "^(.*)%.[^.]*$")
96 if head
then str
= head
end
97 str
= string.match(str
, "^[%w_.%-]+")
101 check(str
, "cannot derive module name, use -n name")
102 return string.gsub(str
, "[%.%-]", "_")
105 ------------------------------------------------------------------------------
107 local function bcsave_tail(fp
, output
, s
)
108 local ok
, err
= fp
:write(s
)
109 if ok
and output
~= "-" then ok
, err
= fp
:close() end
110 check(ok
, "cannot write ", output
, ": ", err
)
113 local function bcsave_raw(output
, s
)
114 local fp
= savefile(output
, "wb")
115 bcsave_tail(fp
, output
, s
)
118 local function bcsave_c(ctx
, output
, s
)
119 local fp
= savefile(output
, "w")
120 if ctx
.type == "c" then
121 fp
:write(string.format([[
126 __declspec(dllexport)
128 const char %s%s[] = {
129 ]], LJBC_PREFIX
, ctx
.modname
))
131 fp
:write(string.format([[
133 static const char %s%s[] = {
134 ]], LJBC_PREFIX
, ctx
.modname
, #s
, LJBC_PREFIX
, ctx
.modname
))
136 local t
, n
, m
= {}, 0, 0
138 local b
= tostring(string.byte(s
, i
))
141 fp
:write(table.concat(t
, ",", 1, n
), ",\n")
147 bcsave_tail(fp
, output
, table.concat(t
, ",", 1, n
).."\n};\n")
150 local function bcsave_elfobj(ctx
, output
, s
, ffi
)
153 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
154 uint16_t type, machine;
156 uint32_t entry, phofs, shofs;
158 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
161 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
162 uint16_t type, machine;
164 uint64_t entry, phofs, shofs;
166 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
169 uint32_t name, type, flags, addr, ofs, size, link, info, align, entsize;
173 uint64_t flags, addr, ofs, size;
175 uint64_t align, entsize;
178 uint32_t name, value, size;
186 uint64_t value, size;
190 ELF32sectheader sect[6];
196 ELF64sectheader sect[6];
201 local symname
= LJBC_PREFIX
..ctx
.modname
202 local is64
, isbe
= false, false
203 if ctx
.arch
== "x64" or ctx
.arch
== "arm64" then
205 elseif ctx
.arch
== "ppc" or ctx
.arch
== "mips" then
209 -- Handle different host/target endianess.
210 local function f32(x
) return x
end
211 local f16
, fofs
= f32
, f32
212 if ffi
.abi("be") ~= isbe
then
214 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
216 local two32
= ffi
.cast("int64_t", 2^
32)
217 function fofs(x
) return bit
.bswap(x
)*two32
end
223 -- Create ELF object and fill in header.
224 local o
= ffi
.new(is64
and "ELF64obj" or "ELF32obj")
226 if ctx
.os
== "bsd" or ctx
.os
== "other" then -- Determine native hdr.eosabi.
227 local bf
= assert(io
.open("/bin/ls", "rb"))
228 local bs
= bf
:read(9)
231 check(hdr
.emagic
[0] == 127, "no support for writing native object files")
233 hdr
.emagic
= "\127ELF"
234 hdr
.eosabi
= ({ freebsd
=9, netbsd
=2, openbsd
=12, solaris
=6 })[ctx
.os
] or 0
236 hdr
.eclass
= is64
and 2 or 1
237 hdr
.eendian
= isbe
and 2 or 1
240 hdr
.machine
= f16(({ x86
=3, x64
=62, arm
=40, arm64
=183, ppc
=20, mips
=8, mipsel
=8 })[ctx
.arch
])
241 if ctx
.arch
== "mips" or ctx
.arch
== "mipsel" then
242 hdr
.flags
= 0x50001006
245 hdr
.shofs
= fofs(ffi
.offsetof(o
, "sect"))
246 hdr
.ehsize
= f16(ffi
.sizeof(hdr
))
247 hdr
.shentsize
= f16(ffi
.sizeof(o
.sect
[0]))
249 hdr
.shstridx
= f16(2)
251 -- Fill in sections and symbols.
252 local sofs
, ofs
= ffi
.offsetof(o
, "space"), 1
253 for i
,name
in ipairs
{
254 ".symtab", ".shstrtab", ".strtab", ".rodata", ".note.GNU-stack",
256 local sect
= o
.sect
[i
]
259 ffi
.copy(o
.space
+ofs
, name
)
262 o
.sect
[1].type = f32(2) -- .symtab
263 o
.sect
[1].link
= f32(3)
264 o
.sect
[1].info
= f32(1)
265 o
.sect
[1].align
= fofs(8)
266 o
.sect
[1].ofs
= fofs(ffi
.offsetof(o
, "sym"))
267 o
.sect
[1].entsize
= fofs(ffi
.sizeof(o
.sym
[0]))
268 o
.sect
[1].size
= fofs(ffi
.sizeof(o
.sym
))
269 o
.sym
[1].name
= f32(1)
270 o
.sym
[1].sectidx
= f16(4)
271 o
.sym
[1].size
= fofs(#s
)
273 o
.sect
[2].type = f32(3) -- .shstrtab
274 o
.sect
[2].ofs
= fofs(sofs
)
275 o
.sect
[2].size
= fofs(ofs
)
276 o
.sect
[3].type = f32(3) -- .strtab
277 o
.sect
[3].ofs
= fofs(sofs
+ ofs
)
278 o
.sect
[3].size
= fofs(#symname
+1)
279 ffi
.copy(o
.space
+ofs
+1, symname
)
280 ofs
= ofs
+ #symname
+ 2
281 o
.sect
[4].type = f32(1) -- .rodata
282 o
.sect
[4].flags
= fofs(2)
283 o
.sect
[4].ofs
= fofs(sofs
+ ofs
)
284 o
.sect
[4].size
= fofs(#s
)
285 o
.sect
[5].type = f32(1) -- .note.GNU-stack
286 o
.sect
[5].ofs
= fofs(sofs
+ ofs
+ #s
)
288 -- Write ELF object file.
289 local fp
= savefile(output
, "wb")
290 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
291 bcsave_tail(fp
, output
, s
)
294 local function bcsave_peobj(ctx
, output
, s
, ffi
)
297 uint16_t arch, nsects;
298 uint32_t time, symtabofs, nsyms;
299 uint16_t opthdrsz, flags;
303 uint32_t vsize, vaddr, size, ofs, relocofs, lineofs;
304 uint16_t nreloc, nline;
307 typedef struct __attribute((packed)) {
317 typedef struct __attribute((packed)) {
319 uint16_t nreloc, nline;
322 uint8_t comdatsel, unused[3];
327 // Must be an even number of symbol structs.
338 local symname
= LJBC_PREFIX
..ctx
.modname
340 if ctx
.arch
== "x86" then
341 symname
= "_"..symname
342 elseif ctx
.arch
== "x64" then
345 local symexport
= " /EXPORT:"..symname
..",DATA "
347 -- The file format is always little-endian. Swap if the host is big-endian.
348 local function f32(x
) return x
end
350 if ffi
.abi("be") then
352 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
355 -- Create PE object and fill in header.
356 local o
= ffi
.new("PEobj")
358 hdr
.arch
= f16(({ x86
=0x14c, x64
=0x8664, arm
=0x1c0, ppc
=0x1f2, mips
=0x366, mipsel
=0x366 })[ctx
.arch
])
360 hdr
.symtabofs
= f32(ffi
.offsetof(o
, "sym0"))
363 -- Fill in sections and symbols.
364 o
.sect
[0].name
= ".drectve"
365 o
.sect
[0].size
= f32(#symexport
)
366 o
.sect
[0].flags
= f32(0x00100a00)
369 o
.sym0
.name
= ".drectve"
371 o
.sym0aux
.size
= f32(#symexport
)
372 o
.sect
[1].name
= ".rdata"
373 o
.sect
[1].size
= f32(#s
)
374 o
.sect
[1].flags
= f32(0x40300040)
377 o
.sym1
.name
= ".rdata"
379 o
.sym1aux
.size
= f32(#s
)
382 o
.sym2
.nameref
[1] = f32(4)
383 o
.sym3
.sect
= f16(-1)
385 o
.sym3
.value
= f32(1)
386 o
.sym3
.name
= "@feat.00" -- Mark as SafeSEH compliant.
387 ffi
.copy(o
.space
, symname
)
388 local ofs
= #symname
+ 1
389 o
.strtabsize
= f32(ofs
+ 4)
390 o
.sect
[0].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
391 ffi
.copy(o
.space
+ ofs
, symexport
)
392 ofs
= ofs
+ #symexport
393 o
.sect
[1].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
395 -- Write PE object file.
396 local fp
= savefile(output
, "wb")
397 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
398 bcsave_tail(fp
, output
, s
)
401 local function bcsave_machobj(ctx
, output
, s
, ffi
)
405 uint32_t magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags;
409 mach_header; uint32_t reserved;
412 uint32_t cmd, cmdsize;
414 uint32_t vmaddr, vmsize, fileoff, filesize;
415 uint32_t maxprot, initprot, nsects, flags;
416 } mach_segment_command;
418 uint32_t cmd, cmdsize;
420 uint64_t vmaddr, vmsize, fileoff, filesize;
421 uint32_t maxprot, initprot, nsects, flags;
422 } mach_segment_command_64;
424 char sectname[16], segname[16];
426 uint32_t offset, align, reloff, nreloc, flags;
427 uint32_t reserved1, reserved2;
430 char sectname[16], segname[16];
432 uint32_t offset, align, reloff, nreloc, flags;
433 uint32_t reserved1, reserved2, reserved3;
436 uint32_t cmd, cmdsize, symoff, nsyms, stroff, strsize;
437 } mach_symtab_command;
452 uint32_t magic, nfat_arch;
456 uint32_t cputype, cpusubtype, offset, size, align;
461 mach_segment_command seg;
463 mach_symtab_command sym;
465 mach_nlist sym_entry;
471 mach_segment_command_64 seg;
473 mach_symtab_command sym;
475 mach_nlist_64 sym_entry;
480 mach_fat_arch fat_arch[2];
483 mach_segment_command seg;
485 mach_symtab_command sym;
487 mach_nlist sym_entry;
491 local symname
= '_'..LJBC_PREFIX
..ctx
.modname
492 local isfat
, is64
, align
, mobj
= false, false, 4, "mach_obj"
493 if ctx
.arch
== "x64" then
494 is64
, align
, mobj
= true, 8, "mach_obj_64"
495 elseif ctx
.arch
== "arm" then
496 isfat
, mobj
= true, "mach_fat_obj"
497 elseif ctx
.arch
== "arm64" then
498 is64
, align
, isfat
, mobj
= true, 8, true, "mach_fat_obj"
500 check(ctx
.arch
== "x86", "unsupported architecture for OSX")
502 local function aligned(v
, a
) return bit
.band(v
+a
-1, -a
) end
503 local be32
= bit
.bswap
-- Mach-O FAT is BE, supported archs are LE.
505 -- Create Mach-O object and fill in header.
506 local o
= ffi
.new(mobj
)
507 local mach_size
= aligned(ffi
.offsetof(o
, "space")+#symname
+2, align
)
508 local cputype
= ({ x86
={7}, x64
={0x01000007}, arm
={7,12}, arm64
={0x01000007,0x0100000c} })[ctx
.arch
]
509 local cpusubtype
= ({ x86
={3}, x64
={3}, arm
={3,9}, arm64
={3,0} })[ctx
.arch
]
511 o
.fat
.magic
= be32(0xcafebabe)
512 o
.fat
.nfat_arch
= be32(#cpusubtype
)
515 -- Fill in sections and symbols.
516 for i
=0,#cpusubtype
-1 do
519 local a
= o
.fat_arch
[i
]
520 a
.cputype
= be32(cputype
[i
+1])
521 a
.cpusubtype
= be32(cpusubtype
[i
+1])
522 -- Subsequent slices overlap each other to share data.
523 ofs
= ffi
.offsetof(o
, "arch") + i
*ffi
.sizeof(o
.arch
[0])
525 a
.size
= be32(mach_size
-ofs
+#s
)
528 a
.hdr
.magic
= is64
and 0xfeedfacf or 0xfeedface
529 a
.hdr
.cputype
= cputype
[i
+1]
530 a
.hdr
.cpusubtype
= cpusubtype
[i
+1]
533 a
.hdr
.sizeofcmds
= ffi
.sizeof(a
.seg
)+ffi
.sizeof(a
.sec
)+ffi
.sizeof(a
.sym
)
534 a
.seg
.cmd
= is64
and 0x19 or 0x1
535 a
.seg
.cmdsize
= ffi
.sizeof(a
.seg
)+ffi
.sizeof(a
.sec
)
537 a
.seg
.fileoff
= mach_size
-ofs
542 ffi
.copy(a
.sec
.sectname
, "__data")
543 ffi
.copy(a
.sec
.segname
, "__DATA")
545 a
.sec
.offset
= mach_size
-ofs
547 a
.sym
.cmdsize
= ffi
.sizeof(a
.sym
)
548 a
.sym
.symoff
= ffi
.offsetof(o
, "sym_entry")-ofs
550 a
.sym
.stroff
= ffi
.offsetof(o
, "sym_entry")+ffi
.sizeof(o
.sym_entry
)-ofs
551 a
.sym
.strsize
= aligned(#symname
+2, align
)
553 o
.sym_entry
.type = 0xf
556 ffi
.copy(o
.space
+1, symname
)
558 -- Write Macho-O object file.
559 local fp
= savefile(output
, "wb")
560 fp
:write(ffi
.string(o
, mach_size
))
561 bcsave_tail(fp
, output
, s
)
564 local function bcsave_obj(ctx
, output
, s
)
565 local ok
, ffi
= pcall(require
, "ffi")
566 check(ok
, "FFI library required to write this file type")
567 if ctx
.os
== "windows" then
568 return bcsave_peobj(ctx
, output
, s
, ffi
)
569 elseif ctx
.os
== "osx" then
570 return bcsave_machobj(ctx
, output
, s
, ffi
)
572 return bcsave_elfobj(ctx
, output
, s
, ffi
)
576 ------------------------------------------------------------------------------
578 local function bclist(input
, output
)
579 local f
= readfile(input
)
580 require("jit.bc").dump(f
, savefile(output
, "w"), true)
583 local function bcsave(ctx
, input
, output
)
584 local f
= readfile(input
)
585 local s
= string.dump(f
, ctx
.strip
)
588 t
= detecttype(output
)
592 bcsave_raw(output
, s
)
594 if not ctx
.modname
then ctx
.modname
= detectmodname(input
) end
596 bcsave_obj(ctx
, output
, s
)
598 bcsave_c(ctx
, output
, s
)
603 local function docmd(...)
608 strip
= true, arch
= jit
.arch
, os
= string.lower(jit
.os
),
609 type = false, modname
= false,
613 if type(a
) == "string" and string.sub(a
, 1, 1) == "-" and a
~= "-" then
615 if a
== "--" then break end
617 local opt
= string.sub(a
, m
, m
)
620 elseif opt
== "s" then
622 elseif opt
== "g" then
625 if arg
[n
] == nil or m
~= #a
then usage() end
627 if n
~= 1 then usage() end
628 arg
[1] = check(loadstring(arg
[1]))
629 elseif opt
== "n" then
630 ctx
.modname
= checkmodname(table.remove(arg
, n
))
631 elseif opt
== "t" then
632 ctx
.type = checkarg(table.remove(arg
, n
), map_type
, "file type")
633 elseif opt
== "a" then
634 ctx
.arch
= checkarg(table.remove(arg
, n
), map_arch
, "architecture")
635 elseif opt
== "o" then
636 ctx
.os
= checkarg(table.remove(arg
, n
), map_os
, "OS name")
647 if #arg
== 0 or #arg
> 2 then usage() end
648 bclist(arg
[1], arg
[2] or "-")
650 if #arg
~= 2 then usage() end
651 bcsave(ctx
, arg
[1], arg
[2])
655 ------------------------------------------------------------------------------
657 -- Public module functions.
659 start
= docmd
-- Process -b command line option.