Drop unused function wrapper.
[luajit-2.0.git] / src / jit / bcsave.lua
blob483788192af9e26c9725bf03afab631fa8d1fead
1 ----------------------------------------------------------------------------
2 -- LuaJIT module to save/list bytecode.
3 --
4 -- Copyright (C) 2005-2023 Mike Pall. All rights reserved.
5 -- Released under the MIT license. See Copyright Notice in luajit.h
6 ----------------------------------------------------------------------------
7 --
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()
27 io.stderr:write[[
28 Save LuaJIT bytecode: luajit -b[options] input output
29 -l Only list bytecode.
30 -s Strip debug info (default).
31 -g Keep debug info.
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)
46 os.exit(1)
47 end
49 local function check(ok, ...)
50 if ok then return ok, ... end
51 io.stderr:write("luajit: ", ...)
52 io.stderr:write("\n")
53 os.exit(1)
54 end
56 local function readfile(ctx, input)
57 if ctx.string then
58 return check(loadstring(input, nil, ctx.mode))
59 elseif ctx.filename then
60 local data
61 if input == "-" then
62 data = io.stdin:read("*a")
63 else
64 local fp = assert(io.open(input, "rb"))
65 data = assert(fp:read("*a"))
66 assert(fp:close())
67 end
68 return check(load(data, ctx.filename, ctx.mode))
69 else
70 if input == "-" then input = nil end
71 return check(loadfile(input, ctx.mode))
72 end
73 end
75 local function savefile(name, mode)
76 if name == "-" then return io.stdout end
77 return check(io.open(name, mode))
78 end
80 local function set_stdout_binary(ffi)
81 ffi.cdef[[int _setmode(int fd, int mode);]]
82 ffi.C._setmode(1, 0x8000)
83 end
85 ------------------------------------------------------------------------------
87 local map_type = {
88 raw = "raw", c = "c", cc = "c", h = "h", o = "obj", obj = "obj",
91 local map_arch = {
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, },
106 local map_os = {
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)
112 str = str:lower()
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_.%-]+")
134 else
135 str = nil
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
162 fp:write(format([[
163 #ifdef __cplusplus
164 extern "C"
165 #endif
166 #ifdef _WIN32
167 __declspec(dllexport)
168 #endif
169 const unsigned char %s%s[] = {
170 ]], LJBC_PREFIX, ctx.modname))
171 else
172 fp:write(format([[
173 #define %s%s_SIZE %d
174 static const unsigned char %s%s[] = {
175 ]], LJBC_PREFIX, ctx.modname, #s, LJBC_PREFIX, ctx.modname))
177 local t, n, m = {}, 0, 0
178 for i=1,#s do
179 local b = tostring(string.byte(s, i))
180 m = m + #b + 1
181 if m > 78 then
182 fp:write(tconcat(t, ",", 1, n), ",\n")
183 n, m = 0, #b + 1
185 n = n + 1
186 t[n] = b
188 bcsave_tail(fp, output, tconcat(t, ",", 1, n).."\n};\n")
191 local function bcsave_elfobj(ctx, output, s, ffi)
192 ffi.cdef[[
193 typedef struct {
194 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
195 uint16_t type, machine;
196 uint32_t version;
197 uint32_t entry, phofs, shofs;
198 uint32_t flags;
199 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
200 } ELF32header;
201 typedef struct {
202 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7];
203 uint16_t type, machine;
204 uint32_t version;
205 uint64_t entry, phofs, shofs;
206 uint32_t flags;
207 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx;
208 } ELF64header;
209 typedef struct {
210 uint32_t name, type, flags, addr, ofs, size, link, info, align, entsize;
211 } ELF32sectheader;
212 typedef struct {
213 uint32_t name, type;
214 uint64_t flags, addr, ofs, size;
215 uint32_t link, info;
216 uint64_t align, entsize;
217 } ELF64sectheader;
218 typedef struct {
219 uint32_t name, value, size;
220 uint8_t info, other;
221 uint16_t sectidx;
222 } ELF32symbol;
223 typedef struct {
224 uint32_t name;
225 uint8_t info, other;
226 uint16_t sectidx;
227 uint64_t value, size;
228 } ELF64symbol;
229 typedef struct {
230 ELF32header hdr;
231 ELF32sectheader sect[6];
232 ELF32symbol sym[2];
233 uint8_t space[4096];
234 } ELF32obj;
235 typedef struct {
236 ELF64header hdr;
237 ELF64sectheader sect[6];
238 ELF64symbol sym[2];
239 uint8_t space[4096];
240 } ELF64obj;
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
250 f32 = bit.bswap
251 function f16(x) return bit.rshift(bit.bswap(x), 16) end
252 if is64 then
253 local two32 = ffi.cast("int64_t", 2^32)
254 function fofs(x) return bit.bswap(x)*two32 end
255 else
256 fofs = f32
260 -- Create ELF object and fill in header.
261 local o = ffi.new(is64 and "ELF64obj" or "ELF32obj")
262 local hdr = o.hdr
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)
266 bf:close()
267 ffi.copy(o, bs, 9)
268 check(hdr.emagic[0] == 127, "no support for writing native object files")
269 else
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
275 hdr.eversion = 1
276 hdr.type = f16(1)
277 hdr.machine = f16(ai.m)
278 hdr.flags = f32(ai.f or 0)
279 hdr.version = f32(1)
280 hdr.shofs = fofs(ffi.offsetof(o, "sect"))
281 hdr.ehsize = f16(ffi.sizeof(hdr))
282 hdr.shentsize = f16(ffi.sizeof(o.sect[0]))
283 hdr.shnum = f16(6)
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",
290 } do
291 local sect = o.sect[i]
292 sect.align = fofs(1)
293 sect.name = f32(ofs)
294 ffi.copy(o.space+ofs, name)
295 ofs = ofs + #name+1
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)
307 o.sym[1].info = 17
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)
330 ffi.cdef[[
331 typedef struct {
332 uint16_t arch, nsects;
333 uint32_t time, symtabofs, nsyms;
334 uint16_t opthdrsz, flags;
335 } PEheader;
336 typedef struct {
337 char name[8];
338 uint32_t vsize, vaddr, size, ofs, relocofs, lineofs;
339 uint16_t nreloc, nline;
340 uint32_t flags;
341 } PEsection;
342 typedef struct __attribute((packed)) {
343 union {
344 char name[8];
345 uint32_t nameref[2];
347 uint32_t value;
348 int16_t sect;
349 uint16_t type;
350 uint8_t scl, naux;
351 } PEsym;
352 typedef struct __attribute((packed)) {
353 uint32_t size;
354 uint16_t nreloc, nline;
355 uint32_t cksum;
356 uint16_t assoc;
357 uint8_t comdatsel, unused[3];
358 } PEsymaux;
359 typedef struct {
360 PEheader hdr;
361 PEsection sect[2];
362 // Must be an even number of symbol structs.
363 PEsym sym0;
364 PEsymaux sym0aux;
365 PEsym sym1;
366 PEsymaux sym1aux;
367 PEsym sym2;
368 PEsym sym3;
369 uint32_t strtabsize;
370 uint8_t space[4096];
371 } PEobj;
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
380 local f16 = f32
381 if ffi.abi("be") then
382 f32 = bit.bswap
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")
388 local hdr = o.hdr
389 hdr.arch = f16(assert(ai.p))
390 hdr.nsects = f16(2)
391 hdr.symtabofs = f32(ffi.offsetof(o, "sym0"))
392 hdr.nsyms = f32(6)
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)
398 o.sym0.sect = f16(1)
399 o.sym0.scl = 3
400 o.sym0.name = ".drectve"
401 o.sym0.naux = 1
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)
406 o.sym1.sect = f16(2)
407 o.sym1.scl = 3
408 o.sym1.name = ".rdata"
409 o.sym1.naux = 1
410 o.sym1aux.size = f32(#s)
411 o.sym2.sect = f16(2)
412 o.sym2.scl = 2
413 o.sym2.nameref[1] = f32(4)
414 o.sym3.sect = f16(-1)
415 o.sym3.scl = 2
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)
433 ffi.cdef[[
434 typedef struct
436 uint32_t magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags;
437 } mach_header;
438 typedef struct
440 mach_header; uint32_t reserved;
441 } mach_header_64;
442 typedef struct {
443 uint32_t cmd, cmdsize;
444 char segname[16];
445 uint64_t vmaddr, vmsize, fileoff, filesize;
446 uint32_t maxprot, initprot, nsects, flags;
447 } mach_segment_command_64;
448 typedef struct {
449 char sectname[16], segname[16];
450 uint64_t addr, size;
451 uint32_t offset, align, reloff, nreloc, flags;
452 uint32_t reserved1, reserved2, reserved3;
453 } mach_section_64;
454 typedef struct {
455 uint32_t cmd, cmdsize, symoff, nsyms, stroff, strsize;
456 } mach_symtab_command;
457 typedef struct {
458 int32_t strx;
459 uint8_t type, sect;
460 uint16_t desc;
461 uint64_t value;
462 } mach_nlist_64;
463 typedef struct {
464 mach_header_64 hdr;
465 mach_segment_command_64 seg;
466 mach_section_64 sec;
467 mach_symtab_command sym;
468 mach_nlist_64 sym_entry;
469 uint8_t space[4096];
470 } mach_obj_64;
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
488 o.hdr.filetype = 1
489 o.hdr.ncmds = 2
490 o.hdr.sizeofcmds = ffi.sizeof(o.seg)+ffi.sizeof(o.sec)+ffi.sizeof(o.sym)
491 o.seg.cmd = 0x19
492 o.seg.cmdsize = ffi.sizeof(o.seg)+ffi.sizeof(o.sec)
493 o.seg.vmsize = #s
494 o.seg.fileoff = mach_size
495 o.seg.filesize = #s
496 o.seg.maxprot = 1
497 o.seg.initprot = 1
498 o.seg.nsects = 1
499 ffi.copy(o.sec.sectname, "__data")
500 ffi.copy(o.sec.segname, "__DATA")
501 o.sec.size = #s
502 o.sec.offset = mach_size
503 o.sym.cmd = 2
504 o.sym.cmdsize = ffi.sizeof(o.sym)
505 o.sym.symoff = ffi.offsetof(o, "sym_entry")
506 o.sym.nsyms = 1
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
510 o.sym_entry.sect = 1
511 o.sym_entry.strx = 1
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)
530 else
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)
545 local t = ctx.type
546 if not t then
547 t = detecttype(output)
548 ctx.type = t
550 if t == "raw" then
551 bcsave_raw(output, s)
552 else
553 if not ctx.modname then ctx.modname = detectmodname(input) end
554 if t == "obj" then
555 bcsave_obj(ctx, output, s)
556 else
557 bcsave_c(ctx, output, s)
562 local function docmd(...)
563 local arg = {...}
564 local n = 1
565 local list = false
566 local ctx = {
567 mode = "bt", arch = jit.arch, os = jit.os:lower(),
568 type = false, modname = false, string = false,
570 local strip = "s"
571 local gc64 = ""
572 while n <= #arg do
573 local a = arg[n]
574 if type(a) == "string" and a:sub(1, 1) == "-" and a ~= "-" then
575 tremove(arg, n)
576 if a == "--" then break end
577 for m=2,#a do
578 local opt = a:sub(m, m)
579 if opt == "l" then
580 list = true
581 elseif opt == "s" then
582 strip = "s"
583 elseif opt == "g" then
584 strip = ""
585 elseif opt == "W" or opt == "X" then
586 gc64 = opt
587 elseif opt == "d" then
588 ctx.mode = ctx.mode .. opt
589 else
590 if arg[n] == nil or m ~= #a then usage() end
591 if opt == "e" then
592 if n ~= 1 then usage() end
593 ctx.string = true
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)
604 else
605 usage()
609 else
610 n = n + 1
613 ctx.mode = ctx.mode .. strip .. gc64
614 if list then
615 if #arg == 0 or #arg > 2 then usage() end
616 bclist(ctx, arg[1], arg[2] or "-")
617 else
618 if #arg ~= 2 then usage() end
619 bcsave(ctx, arg[1], arg[2])
623 ------------------------------------------------------------------------------
625 -- Public module functions.
626 return {
627 start = docmd -- Process -b command line option.