1 ----------------------------------------------------------------------------
2 -- LuaJIT module to save/list bytecode.
4 -- Copyright (C) 2005-2023 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
== 20199, "LuaJIT core/library version mismatch")
15 local bit
= require("bit")
17 -- Symbol name prefix for LuaJIT bytecode.
18 local LJBC_PREFIX
= "luaJIT_BC_"
20 local type, assert = type, assert
21 local format = string.format
22 local tremove, tconcat
= table.remove, table.concat
24 ------------------------------------------------------------------------------
26 local function usage()
28 Save LuaJIT bytecode: luajit -b[options] input output
29 -l Only list bytecode.
30 -s Strip debug info (default).
32 -W Generate 32 bit (non-GC64) bytecode.
33 -X Generate 64 bit (GC64) bytecode.
34 -d Generate bytecode in deterministic manner.
35 -n name Set module name (default: auto-detect from input name).
36 -t type Set output file type (default: auto-detect from output name).
37 -a arch Override architecture for object files (default: native).
38 -o os Override OS for object files (default: native).
39 -F name Override filename (default: input filename).
40 -e chunk Use chunk string as input.
41 -- Stop handling options.
42 - Use stdin as input and/or stdout as output.
44 File types: c cc h obj o raw (default)
49 local function check(ok
, ...)
50 if ok
then return ok
, ... end
51 io
.stderr
:write("luajit: ", ...)
56 local function readfile(ctx
, input
)
58 return check(loadstring(input
, nil, ctx
.mode
))
59 elseif ctx
.filename
then
62 data
= io
.stdin
:read("*a")
64 local fp
= assert(io
.open(input
, "rb"))
65 data
= assert(fp
:read("*a"))
68 return check(load(data
, ctx
.filename
, ctx
.mode
))
70 if input
== "-" then input
= nil end
71 return check(loadfile(input
, ctx
.mode
))
75 local function savefile(name
, mode
)
76 if name
== "-" then return io
.stdout
end
77 return check(io
.open(name
, mode
))
80 local function set_stdout_binary(ffi
)
81 ffi
.cdef
[[int _setmode(int fd, int mode);]]
82 ffi
.C
._setmode(1, 0x8000)
85 ------------------------------------------------------------------------------
88 raw
= "raw", c
= "c", cc
= "c", h
= "h", o
= "obj", obj
= "obj",
92 x86
= { e
= "le", b
= 32, m
= 3, p
= 0x14c, },
93 x64
= { e
= "le", b
= 64, m
= 62, p
= 0x8664, },
94 arm
= { e
= "le", b
= 32, m
= 40, p
= 0x1c0, },
95 arm64
= { e
= "le", b
= 64, m
= 183, p
= 0xaa64, },
96 arm64be
= { e
= "be", b
= 64, m
= 183, },
97 ppc
= { e
= "be", b
= 32, m
= 20, },
98 mips
= { e
= "be", b
= 32, m
= 8, f
= 0x50001006, },
99 mipsel
= { e
= "le", b
= 32, m
= 8, f
= 0x50001006, },
100 mips64
= { e
= "be", b
= 64, m
= 8, f
= 0x80000007, },
101 mips64el
= { e
= "le", b
= 64, m
= 8, f
= 0x80000007, },
102 mips64r6
= { e
= "be", b
= 64, m
= 8, f
= 0xa0000407, },
103 mips64r6el
= { e
= "le", b
= 64, m
= 8, f
= 0xa0000407, },
107 linux
= true, windows
= true, osx
= true, freebsd
= true, netbsd
= true,
108 openbsd
= true, dragonfly
= true, solaris
= true,
111 local function checkarg(str
, map
, err
)
113 local s
= check(map
[str
], "unknown ", err
)
114 return type(s
) == "string" and s
or str
117 local function detecttype(str
)
118 local ext
= str
:lower():match("%.(%a+)$")
119 return map_type
[ext
] or "raw"
122 local function checkmodname(str
)
123 check(str
:match("^[%w_.%-]+$"), "bad module name")
124 return str
:gsub("[%.%-]", "_")
127 local function detectmodname(str
)
128 if type(str
) == "string" then
129 local tail
= str
:match("[^/\\]+$")
130 if tail
then str
= tail
end
131 local head
= str
:match("^(.*)%.[^.]*$")
132 if head
then str
= head
end
133 str
= str
:match("^[%w_.%-]+")
137 check(str
, "cannot derive module name, use -n name")
138 return str
:gsub("[%.%-]", "_")
141 ------------------------------------------------------------------------------
143 local function bcsave_tail(fp
, output
, s
)
144 local ok
, err
= fp
:write(s
)
145 if ok
and output
~= "-" then ok
, err
= fp
:close() end
146 check(ok
, "cannot write ", output
, ": ", err
)
149 local function bcsave_raw(output
, s
)
150 if output
== "-" and jit
.os
== "Windows" then
151 local ok
, ffi
= pcall(require
, "ffi")
152 check(ok
, "FFI library required to write binary file to stdout")
153 set_stdout_binary(ffi
)
155 local fp
= savefile(output
, "wb")
156 bcsave_tail(fp
, output
, s
)
159 local function bcsave_c(ctx
, output
, s
)
160 local fp
= savefile(output
, "w")
161 if ctx
.type == "c" then
167 __declspec(dllexport)
169 const unsigned char %s%s[] = {
170 ]], LJBC_PREFIX
, ctx
.modname
))
174 static const unsigned char %s%s[] = {
175 ]], LJBC_PREFIX
, ctx
.modname
, #s
, LJBC_PREFIX
, ctx
.modname
))
177 local t
, n
, m
= {}, 0, 0
179 local b
= tostring(string.byte(s
, i
))
182 fp
:write(tconcat(t
, ",", 1, n
), ",\n")
188 bcsave_tail(fp
, output
, tconcat(t
, ",", 1, n
).."\n};\n")
191 local function bcsave_elfobj(ctx
, output
, s
, ffi
)
194 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
195 uint16_t type, machine;
197 uint32_t entry, phofs, shofs;
199 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
202 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
203 uint16_t type, machine;
205 uint64_t entry, phofs, shofs;
207 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
210 uint32_t name, type, flags, addr, ofs, size, link, info, align, entsize;
214 uint64_t flags, addr, ofs, size;
216 uint64_t align, entsize;
219 uint32_t name, value, size;
227 uint64_t value, size;
231 ELF32sectheader sect[6];
237 ELF64sectheader sect[6];
242 local symname
= LJBC_PREFIX
..ctx
.modname
243 local ai
= assert(map_arch
[ctx
.arch
])
244 local is64
, isbe
= ai
.b
== 64, ai
.e
== "be"
246 -- Handle different host/target endianess.
247 local function f32(x
) return x
end
248 local f16
, fofs
= f32
, f32
249 if ffi
.abi("be") ~= isbe
then
251 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
253 local two32
= ffi
.cast("int64_t", 2^
32)
254 function fofs(x
) return bit
.bswap(x
)*two32
end
260 -- Create ELF object and fill in header.
261 local o
= ffi
.new(is64
and "ELF64obj" or "ELF32obj")
263 if ctx
.os
== "bsd" or ctx
.os
== "other" then -- Determine native hdr.eosabi.
264 local bf
= assert(io
.open("/bin/ls", "rb"))
265 local bs
= bf
:read(9)
268 check(hdr
.emagic
[0] == 127, "no support for writing native object files")
270 hdr
.emagic
= "\127ELF"
271 hdr
.eosabi
= ({ freebsd
=9, netbsd
=2, openbsd
=12, solaris
=6 })[ctx
.os
] or 0
273 hdr
.eclass
= is64
and 2 or 1
274 hdr
.eendian
= isbe
and 2 or 1
277 hdr
.machine
= f16(ai
.m
)
278 hdr
.flags
= f32(ai
.f
or 0)
280 hdr
.shofs
= fofs(ffi
.offsetof(o
, "sect"))
281 hdr
.ehsize
= f16(ffi
.sizeof(hdr
))
282 hdr
.shentsize
= f16(ffi
.sizeof(o
.sect
[0]))
284 hdr
.shstridx
= f16(2)
286 -- Fill in sections and symbols.
287 local sofs
, ofs
= ffi
.offsetof(o
, "space"), 1
288 for i
,name
in ipairs
{
289 ".symtab", ".shstrtab", ".strtab", ".rodata", ".note.GNU-stack",
291 local sect
= o
.sect
[i
]
294 ffi
.copy(o
.space
+ofs
, name
)
297 o
.sect
[1].type = f32(2) -- .symtab
298 o
.sect
[1].link
= f32(3)
299 o
.sect
[1].info
= f32(1)
300 o
.sect
[1].align
= fofs(8)
301 o
.sect
[1].ofs
= fofs(ffi
.offsetof(o
, "sym"))
302 o
.sect
[1].entsize
= fofs(ffi
.sizeof(o
.sym
[0]))
303 o
.sect
[1].size
= fofs(ffi
.sizeof(o
.sym
))
304 o
.sym
[1].name
= f32(1)
305 o
.sym
[1].sectidx
= f16(4)
306 o
.sym
[1].size
= fofs(#s
)
308 o
.sect
[2].type = f32(3) -- .shstrtab
309 o
.sect
[2].ofs
= fofs(sofs
)
310 o
.sect
[2].size
= fofs(ofs
)
311 o
.sect
[3].type = f32(3) -- .strtab
312 o
.sect
[3].ofs
= fofs(sofs
+ ofs
)
313 o
.sect
[3].size
= fofs(#symname
+2)
314 ffi
.copy(o
.space
+ofs
+1, symname
)
315 ofs
= ofs
+ #symname
+ 2
316 o
.sect
[4].type = f32(1) -- .rodata
317 o
.sect
[4].flags
= fofs(2)
318 o
.sect
[4].ofs
= fofs(sofs
+ ofs
)
319 o
.sect
[4].size
= fofs(#s
)
320 o
.sect
[5].type = f32(1) -- .note.GNU-stack
321 o
.sect
[5].ofs
= fofs(sofs
+ ofs
+ #s
)
323 -- Write ELF object file.
324 local fp
= savefile(output
, "wb")
325 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
326 bcsave_tail(fp
, output
, s
)
329 local function bcsave_peobj(ctx
, output
, s
, ffi
)
332 uint16_t arch, nsects;
333 uint32_t time, symtabofs, nsyms;
334 uint16_t opthdrsz, flags;
338 uint32_t vsize, vaddr, size, ofs, relocofs, lineofs;
339 uint16_t nreloc, nline;
342 typedef struct __attribute((packed)) {
352 typedef struct __attribute((packed)) {
354 uint16_t nreloc, nline;
357 uint8_t comdatsel, unused[3];
362 // Must be an even number of symbol structs.
373 local symname
= LJBC_PREFIX
..ctx
.modname
374 local ai
= assert(map_arch
[ctx
.arch
])
375 local is64
= ai
.b
== 64
376 local symexport
= " /EXPORT:"..symname
..",DATA "
378 -- The file format is always little-endian. Swap if the host is big-endian.
379 local function f32(x
) return x
end
381 if ffi
.abi("be") then
383 function f16(x
) return bit
.rshift(bit
.bswap(x
), 16) end
386 -- Create PE object and fill in header.
387 local o
= ffi
.new("PEobj")
389 hdr
.arch
= f16(assert(ai
.p
))
391 hdr
.symtabofs
= f32(ffi
.offsetof(o
, "sym0"))
394 -- Fill in sections and symbols.
395 o
.sect
[0].name
= ".drectve"
396 o
.sect
[0].size
= f32(#symexport
)
397 o
.sect
[0].flags
= f32(0x00100a00)
400 o
.sym0
.name
= ".drectve"
402 o
.sym0aux
.size
= f32(#symexport
)
403 o
.sect
[1].name
= ".rdata"
404 o
.sect
[1].size
= f32(#s
)
405 o
.sect
[1].flags
= f32(0x40300040)
408 o
.sym1
.name
= ".rdata"
410 o
.sym1aux
.size
= f32(#s
)
413 o
.sym2
.nameref
[1] = f32(4)
414 o
.sym3
.sect
= f16(-1)
416 o
.sym3
.value
= f32(1)
417 o
.sym3
.name
= "@feat.00" -- Mark as SafeSEH compliant.
418 ffi
.copy(o
.space
, symname
)
419 local ofs
= #symname
+ 1
420 o
.strtabsize
= f32(ofs
+ 4)
421 o
.sect
[0].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
422 ffi
.copy(o
.space
+ ofs
, symexport
)
423 ofs
= ofs
+ #symexport
424 o
.sect
[1].ofs
= f32(ffi
.offsetof(o
, "space") + ofs
)
426 -- Write PE object file.
427 local fp
= savefile(output
, "wb")
428 fp
:write(ffi
.string(o
, ffi
.sizeof(o
)-4096+ofs
))
429 bcsave_tail(fp
, output
, s
)
432 local function bcsave_machobj(ctx
, output
, s
, ffi
)
436 uint32_t magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags;
440 mach_header; uint32_t reserved;
443 uint32_t cmd, cmdsize;
445 uint64_t vmaddr, vmsize, fileoff, filesize;
446 uint32_t maxprot, initprot, nsects, flags;
447 } mach_segment_command_64;
449 char sectname[16], segname[16];
451 uint32_t offset, align, reloff, nreloc, flags;
452 uint32_t reserved1, reserved2, reserved3;
455 uint32_t cmd, cmdsize, symoff, nsyms, stroff, strsize;
456 } mach_symtab_command;
465 mach_segment_command_64 seg;
467 mach_symtab_command sym;
468 mach_nlist_64 sym_entry;
472 local symname
= '_'..LJBC_PREFIX
..ctx
.modname
473 local cputype
, cpusubtype
= 0x01000007, 3
474 if ctx
.arch
~= "x64" then
475 check(ctx
.arch
== "arm64", "unsupported architecture for OSX")
476 cputype
, cpusubtype
= 0x0100000c, 0
478 local function aligned(v
, a
) return bit
.band(v
+a
-1, -a
) end
480 -- Create Mach-O object and fill in header.
481 local o
= ffi
.new("mach_obj_64")
482 local mach_size
= aligned(ffi
.offsetof(o
, "space")+#symname
+2, 8)
484 -- Fill in sections and symbols.
485 o
.hdr
.magic
= 0xfeedfacf
486 o
.hdr
.cputype
= cputype
487 o
.hdr
.cpusubtype
= cpusubtype
490 o
.hdr
.sizeofcmds
= ffi
.sizeof(o
.seg
)+ffi
.sizeof(o
.sec
)+ffi
.sizeof(o
.sym
)
492 o
.seg
.cmdsize
= ffi
.sizeof(o
.seg
)+ffi
.sizeof(o
.sec
)
494 o
.seg
.fileoff
= mach_size
499 ffi
.copy(o
.sec
.sectname
, "__data")
500 ffi
.copy(o
.sec
.segname
, "__DATA")
502 o
.sec
.offset
= mach_size
504 o
.sym
.cmdsize
= ffi
.sizeof(o
.sym
)
505 o
.sym
.symoff
= ffi
.offsetof(o
, "sym_entry")
507 o
.sym
.stroff
= ffi
.offsetof(o
, "sym_entry")+ffi
.sizeof(o
.sym_entry
)
508 o
.sym
.strsize
= aligned(#symname
+2, 8)
509 o
.sym_entry
.type = 0xf
512 ffi
.copy(o
.space
+1, symname
)
514 -- Write Mach-O object file.
515 local fp
= savefile(output
, "wb")
516 fp
:write(ffi
.string(o
, mach_size
))
517 bcsave_tail(fp
, output
, s
)
520 local function bcsave_obj(ctx
, output
, s
)
521 local ok
, ffi
= pcall(require
, "ffi")
522 check(ok
, "FFI library required to write this file type")
523 if output
== "-" and jit
.os
== "Windows" then
524 set_stdout_binary(ffi
)
526 if ctx
.os
== "windows" then
527 return bcsave_peobj(ctx
, output
, s
, ffi
)
528 elseif ctx
.os
== "osx" then
529 return bcsave_machobj(ctx
, output
, s
, ffi
)
531 return bcsave_elfobj(ctx
, output
, s
, ffi
)
535 ------------------------------------------------------------------------------
537 local function bclist(ctx
, input
, output
)
538 local f
= readfile(ctx
, input
)
539 require("jit.bc").dump(f
, savefile(output
, "w"), true)
542 local function bcsave(ctx
, input
, output
)
543 local f
= readfile(ctx
, input
)
544 local s
= string.dump(f
, ctx
.mode
)
547 t
= detecttype(output
)
551 bcsave_raw(output
, s
)
553 if not ctx
.modname
then ctx
.modname
= detectmodname(input
) end
555 bcsave_obj(ctx
, output
, s
)
557 bcsave_c(ctx
, output
, s
)
562 local function docmd(...)
567 mode
= "bt", arch
= jit
.arch
, os
= jit
.os
:lower(),
568 type = false, modname
= false, string = false,
574 if type(a
) == "string" and a
:sub(1, 1) == "-" and a
~= "-" then
576 if a
== "--" then break end
578 local opt
= a
:sub(m
, m
)
581 elseif opt
== "s" then
583 elseif opt
== "g" then
585 elseif opt
== "W" or opt
== "X" then
587 elseif opt
== "d" then
588 ctx
.mode
= ctx
.mode
.. opt
590 if arg
[n
] == nil or m
~= #a
then usage() end
592 if n
~= 1 then usage() end
594 elseif opt
== "n" then
595 ctx
.modname
= checkmodname(tremove(arg
, n
))
596 elseif opt
== "t" then
597 ctx
.type = checkarg(tremove(arg
, n
), map_type
, "file type")
598 elseif opt
== "a" then
599 ctx
.arch
= checkarg(tremove(arg
, n
), map_arch
, "architecture")
600 elseif opt
== "o" then
601 ctx
.os
= checkarg(tremove(arg
, n
), map_os
, "OS name")
602 elseif opt
== "F" then
603 ctx
.filename
= "@"..tremove(arg
, n
)
613 ctx
.mode
= ctx
.mode
.. strip
.. gc64
615 if #arg
== 0 or #arg
> 2 then usage() end
616 bclist(ctx
, arg
[1], arg
[2] or "-")
618 if #arg
~= 2 then usage() end
619 bcsave(ctx
, arg
[1], arg
[2])
623 ------------------------------------------------------------------------------
625 -- Public module functions.
627 start
= docmd
-- Process -b command line option.