bcsave.lua (18267B)
1 ---------------------------------------------------------------------------- 2 -- LuaJIT module to save/list bytecode. 3 -- 4 -- Copyright (C) 2005-2016 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. 10 -- 11 ------------------------------------------------------------------------------ 12 13 local jit = require("jit") 14 assert(jit.version_num == 20100, "LuaJIT core/library version mismatch") 15 local bit = require("bit") 16 17 -- Symbol name prefix for LuaJIT bytecode. 18 local LJBC_PREFIX = "luaJIT_BC_" 19 20 ------------------------------------------------------------------------------ 21 22 local function usage() 23 io.stderr:write[[ 24 Save LuaJIT bytecode: luajit -b[options] input output 25 -l Only list bytecode. 26 -s Strip debug info (default). 27 -g Keep debug info. 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. 35 36 File types: c h obj o raw (default) 37 ]] 38 os.exit(1) 39 end 40 41 local function check(ok, ...) 42 if ok then return ok, ... end 43 io.stderr:write("luajit: ", ...) 44 io.stderr:write("\n") 45 os.exit(1) 46 end 47 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)) 52 end 53 54 local function savefile(name, mode) 55 if name == "-" then return io.stdout end 56 return check(io.open(name, mode)) 57 end 58 59 ------------------------------------------------------------------------------ 60 61 local map_type = { 62 raw = "raw", c = "c", h = "h", o = "obj", obj = "obj", 63 } 64 65 local map_arch = { 66 x86 = true, x64 = true, arm = true, arm64 = true, ppc = true, 67 mips = true, mipsel = true, 68 } 69 70 local map_os = { 71 linux = true, windows = true, osx = true, freebsd = true, netbsd = true, 72 openbsd = true, dragonfly = true, solaris = true, 73 } 74 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 79 end 80 81 local function detecttype(str) 82 local ext = string.match(string.lower(str), "%.(%a+)$") 83 return map_type[ext] or "raw" 84 end 85 86 local function checkmodname(str) 87 check(string.match(str, "^[%w_.%-]+$"), "bad module name") 88 return string.gsub(str, "[%.%-]", "_") 89 end 90 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_.%-]+") 98 else 99 str = nil 100 end 101 check(str, "cannot derive module name, use -n name") 102 return string.gsub(str, "[%.%-]", "_") 103 end 104 105 ------------------------------------------------------------------------------ 106 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) 111 end 112 113 local function bcsave_raw(output, s) 114 local fp = savefile(output, "wb") 115 bcsave_tail(fp, output, s) 116 end 117 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([[ 122 #ifdef _cplusplus 123 extern "C" 124 #endif 125 #ifdef _WIN32 126 __declspec(dllexport) 127 #endif 128 const char %s%s[] = { 129 ]], LJBC_PREFIX, ctx.modname)) 130 else 131 fp:write(string.format([[ 132 #define %s%s_SIZE %d 133 static const char %s%s[] = { 134 ]], LJBC_PREFIX, ctx.modname, #s, LJBC_PREFIX, ctx.modname)) 135 end 136 local t, n, m = {}, 0, 0 137 for i=1,#s do 138 local b = tostring(string.byte(s, i)) 139 m = m + #b + 1 140 if m > 78 then 141 fp:write(table.concat(t, ",", 1, n), ",\n") 142 n, m = 0, #b + 1 143 end 144 n = n + 1 145 t[n] = b 146 end 147 bcsave_tail(fp, output, table.concat(t, ",", 1, n).."\n};\n") 148 end 149 150 local function bcsave_elfobj(ctx, output, s, ffi) 151 ffi.cdef[[ 152 typedef struct { 153 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7]; 154 uint16_t type, machine; 155 uint32_t version; 156 uint32_t entry, phofs, shofs; 157 uint32_t flags; 158 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx; 159 } ELF32header; 160 typedef struct { 161 uint8_t emagic[4], eclass, eendian, eversion, eosabi, eabiversion, epad[7]; 162 uint16_t type, machine; 163 uint32_t version; 164 uint64_t entry, phofs, shofs; 165 uint32_t flags; 166 uint16_t ehsize, phentsize, phnum, shentsize, shnum, shstridx; 167 } ELF64header; 168 typedef struct { 169 uint32_t name, type, flags, addr, ofs, size, link, info, align, entsize; 170 } ELF32sectheader; 171 typedef struct { 172 uint32_t name, type; 173 uint64_t flags, addr, ofs, size; 174 uint32_t link, info; 175 uint64_t align, entsize; 176 } ELF64sectheader; 177 typedef struct { 178 uint32_t name, value, size; 179 uint8_t info, other; 180 uint16_t sectidx; 181 } ELF32symbol; 182 typedef struct { 183 uint32_t name; 184 uint8_t info, other; 185 uint16_t sectidx; 186 uint64_t value, size; 187 } ELF64symbol; 188 typedef struct { 189 ELF32header hdr; 190 ELF32sectheader sect[6]; 191 ELF32symbol sym[2]; 192 uint8_t space[4096]; 193 } ELF32obj; 194 typedef struct { 195 ELF64header hdr; 196 ELF64sectheader sect[6]; 197 ELF64symbol sym[2]; 198 uint8_t space[4096]; 199 } ELF64obj; 200 ]] 201 local symname = LJBC_PREFIX..ctx.modname 202 local is64, isbe = false, false 203 if ctx.arch == "x64" or ctx.arch == "arm64" then 204 is64 = true 205 elseif ctx.arch == "ppc" or ctx.arch == "mips" then 206 isbe = true 207 end 208 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 213 f32 = bit.bswap 214 function f16(x) return bit.rshift(bit.bswap(x), 16) end 215 if is64 then 216 local two32 = ffi.cast("int64_t", 2^32) 217 function fofs(x) return bit.bswap(x)*two32 end 218 else 219 fofs = f32 220 end 221 end 222 223 -- Create ELF object and fill in header. 224 local o = ffi.new(is64 and "ELF64obj" or "ELF32obj") 225 local hdr = o.hdr 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) 229 bf:close() 230 ffi.copy(o, bs, 9) 231 check(hdr.emagic[0] == 127, "no support for writing native object files") 232 else 233 hdr.emagic = "\127ELF" 234 hdr.eosabi = ({ freebsd=9, netbsd=2, openbsd=12, solaris=6 })[ctx.os] or 0 235 end 236 hdr.eclass = is64 and 2 or 1 237 hdr.eendian = isbe and 2 or 1 238 hdr.eversion = 1 239 hdr.type = f16(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 243 end 244 hdr.version = f32(1) 245 hdr.shofs = fofs(ffi.offsetof(o, "sect")) 246 hdr.ehsize = f16(ffi.sizeof(hdr)) 247 hdr.shentsize = f16(ffi.sizeof(o.sect[0])) 248 hdr.shnum = f16(6) 249 hdr.shstridx = f16(2) 250 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", 255 } do 256 local sect = o.sect[i] 257 sect.align = fofs(1) 258 sect.name = f32(ofs) 259 ffi.copy(o.space+ofs, name) 260 ofs = ofs + #name+1 261 end 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) 272 o.sym[1].info = 17 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) 287 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) 292 end 293 294 local function bcsave_peobj(ctx, output, s, ffi) 295 ffi.cdef[[ 296 typedef struct { 297 uint16_t arch, nsects; 298 uint32_t time, symtabofs, nsyms; 299 uint16_t opthdrsz, flags; 300 } PEheader; 301 typedef struct { 302 char name[8]; 303 uint32_t vsize, vaddr, size, ofs, relocofs, lineofs; 304 uint16_t nreloc, nline; 305 uint32_t flags; 306 } PEsection; 307 typedef struct __attribute((packed)) { 308 union { 309 char name[8]; 310 uint32_t nameref[2]; 311 }; 312 uint32_t value; 313 int16_t sect; 314 uint16_t type; 315 uint8_t scl, naux; 316 } PEsym; 317 typedef struct __attribute((packed)) { 318 uint32_t size; 319 uint16_t nreloc, nline; 320 uint32_t cksum; 321 uint16_t assoc; 322 uint8_t comdatsel, unused[3]; 323 } PEsymaux; 324 typedef struct { 325 PEheader hdr; 326 PEsection sect[2]; 327 // Must be an even number of symbol structs. 328 PEsym sym0; 329 PEsymaux sym0aux; 330 PEsym sym1; 331 PEsymaux sym1aux; 332 PEsym sym2; 333 PEsym sym3; 334 uint32_t strtabsize; 335 uint8_t space[4096]; 336 } PEobj; 337 ]] 338 local symname = LJBC_PREFIX..ctx.modname 339 local is64 = false 340 if ctx.arch == "x86" then 341 symname = "_"..symname 342 elseif ctx.arch == "x64" then 343 is64 = true 344 end 345 local symexport = " /EXPORT:"..symname..",DATA " 346 347 -- The file format is always little-endian. Swap if the host is big-endian. 348 local function f32(x) return x end 349 local f16 = f32 350 if ffi.abi("be") then 351 f32 = bit.bswap 352 function f16(x) return bit.rshift(bit.bswap(x), 16) end 353 end 354 355 -- Create PE object and fill in header. 356 local o = ffi.new("PEobj") 357 local hdr = o.hdr 358 hdr.arch = f16(({ x86=0x14c, x64=0x8664, arm=0x1c0, ppc=0x1f2, mips=0x366, mipsel=0x366 })[ctx.arch]) 359 hdr.nsects = f16(2) 360 hdr.symtabofs = f32(ffi.offsetof(o, "sym0")) 361 hdr.nsyms = f32(6) 362 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) 367 o.sym0.sect = f16(1) 368 o.sym0.scl = 3 369 o.sym0.name = ".drectve" 370 o.sym0.naux = 1 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) 375 o.sym1.sect = f16(2) 376 o.sym1.scl = 3 377 o.sym1.name = ".rdata" 378 o.sym1.naux = 1 379 o.sym1aux.size = f32(#s) 380 o.sym2.sect = f16(2) 381 o.sym2.scl = 2 382 o.sym2.nameref[1] = f32(4) 383 o.sym3.sect = f16(-1) 384 o.sym3.scl = 2 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) 394 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) 399 end 400 401 local function bcsave_machobj(ctx, output, s, ffi) 402 ffi.cdef[[ 403 typedef struct 404 { 405 uint32_t magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags; 406 } mach_header; 407 typedef struct 408 { 409 mach_header; uint32_t reserved; 410 } mach_header_64; 411 typedef struct { 412 uint32_t cmd, cmdsize; 413 char segname[16]; 414 uint32_t vmaddr, vmsize, fileoff, filesize; 415 uint32_t maxprot, initprot, nsects, flags; 416 } mach_segment_command; 417 typedef struct { 418 uint32_t cmd, cmdsize; 419 char segname[16]; 420 uint64_t vmaddr, vmsize, fileoff, filesize; 421 uint32_t maxprot, initprot, nsects, flags; 422 } mach_segment_command_64; 423 typedef struct { 424 char sectname[16], segname[16]; 425 uint32_t addr, size; 426 uint32_t offset, align, reloff, nreloc, flags; 427 uint32_t reserved1, reserved2; 428 } mach_section; 429 typedef struct { 430 char sectname[16], segname[16]; 431 uint64_t addr, size; 432 uint32_t offset, align, reloff, nreloc, flags; 433 uint32_t reserved1, reserved2, reserved3; 434 } mach_section_64; 435 typedef struct { 436 uint32_t cmd, cmdsize, symoff, nsyms, stroff, strsize; 437 } mach_symtab_command; 438 typedef struct { 439 int32_t strx; 440 uint8_t type, sect; 441 int16_t desc; 442 uint32_t value; 443 } mach_nlist; 444 typedef struct { 445 uint32_t strx; 446 uint8_t type, sect; 447 uint16_t desc; 448 uint64_t value; 449 } mach_nlist_64; 450 typedef struct 451 { 452 uint32_t magic, nfat_arch; 453 } mach_fat_header; 454 typedef struct 455 { 456 uint32_t cputype, cpusubtype, offset, size, align; 457 } mach_fat_arch; 458 typedef struct { 459 struct { 460 mach_header hdr; 461 mach_segment_command seg; 462 mach_section sec; 463 mach_symtab_command sym; 464 } arch[1]; 465 mach_nlist sym_entry; 466 uint8_t space[4096]; 467 } mach_obj; 468 typedef struct { 469 struct { 470 mach_header_64 hdr; 471 mach_segment_command_64 seg; 472 mach_section_64 sec; 473 mach_symtab_command sym; 474 } arch[1]; 475 mach_nlist_64 sym_entry; 476 uint8_t space[4096]; 477 } mach_obj_64; 478 typedef struct { 479 mach_fat_header fat; 480 mach_fat_arch fat_arch[2]; 481 struct { 482 mach_header hdr; 483 mach_segment_command seg; 484 mach_section sec; 485 mach_symtab_command sym; 486 } arch[2]; 487 mach_nlist sym_entry; 488 uint8_t space[4096]; 489 } mach_fat_obj; 490 ]] 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" 499 else 500 check(ctx.arch == "x86", "unsupported architecture for OSX") 501 end 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. 504 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] 510 if isfat then 511 o.fat.magic = be32(0xcafebabe) 512 o.fat.nfat_arch = be32(#cpusubtype) 513 end 514 515 -- Fill in sections and symbols. 516 for i=0,#cpusubtype-1 do 517 local ofs = 0 518 if isfat then 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]) 524 a.offset = be32(ofs) 525 a.size = be32(mach_size-ofs+#s) 526 end 527 local a = o.arch[i] 528 a.hdr.magic = is64 and 0xfeedfacf or 0xfeedface 529 a.hdr.cputype = cputype[i+1] 530 a.hdr.cpusubtype = cpusubtype[i+1] 531 a.hdr.filetype = 1 532 a.hdr.ncmds = 2 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) 536 a.seg.vmsize = #s 537 a.seg.fileoff = mach_size-ofs 538 a.seg.filesize = #s 539 a.seg.maxprot = 1 540 a.seg.initprot = 1 541 a.seg.nsects = 1 542 ffi.copy(a.sec.sectname, "__data") 543 ffi.copy(a.sec.segname, "__DATA") 544 a.sec.size = #s 545 a.sec.offset = mach_size-ofs 546 a.sym.cmd = 2 547 a.sym.cmdsize = ffi.sizeof(a.sym) 548 a.sym.symoff = ffi.offsetof(o, "sym_entry")-ofs 549 a.sym.nsyms = 1 550 a.sym.stroff = ffi.offsetof(o, "sym_entry")+ffi.sizeof(o.sym_entry)-ofs 551 a.sym.strsize = aligned(#symname+2, align) 552 end 553 o.sym_entry.type = 0xf 554 o.sym_entry.sect = 1 555 o.sym_entry.strx = 1 556 ffi.copy(o.space+1, symname) 557 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) 562 end 563 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) 571 else 572 return bcsave_elfobj(ctx, output, s, ffi) 573 end 574 end 575 576 ------------------------------------------------------------------------------ 577 578 local function bclist(input, output) 579 local f = readfile(input) 580 require("jit.bc").dump(f, savefile(output, "w"), true) 581 end 582 583 local function bcsave(ctx, input, output) 584 local f = readfile(input) 585 local s = string.dump(f, ctx.strip) 586 local t = ctx.type 587 if not t then 588 t = detecttype(output) 589 ctx.type = t 590 end 591 if t == "raw" then 592 bcsave_raw(output, s) 593 else 594 if not ctx.modname then ctx.modname = detectmodname(input) end 595 if t == "obj" then 596 bcsave_obj(ctx, output, s) 597 else 598 bcsave_c(ctx, output, s) 599 end 600 end 601 end 602 603 local function docmd(...) 604 local arg = {...} 605 local n = 1 606 local list = false 607 local ctx = { 608 strip = true, arch = jit.arch, os = string.lower(jit.os), 609 type = false, modname = false, 610 } 611 while n <= #arg do 612 local a = arg[n] 613 if type(a) == "string" and string.sub(a, 1, 1) == "-" and a ~= "-" then 614 table.remove(arg, n) 615 if a == "--" then break end 616 for m=2,#a do 617 local opt = string.sub(a, m, m) 618 if opt == "l" then 619 list = true 620 elseif opt == "s" then 621 ctx.strip = true 622 elseif opt == "g" then 623 ctx.strip = false 624 else 625 if arg[n] == nil or m ~= #a then usage() end 626 if opt == "e" then 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") 637 else 638 usage() 639 end 640 end 641 end 642 else 643 n = n + 1 644 end 645 end 646 if list then 647 if #arg == 0 or #arg > 2 then usage() end 648 bclist(arg[1], arg[2] or "-") 649 else 650 if #arg ~= 2 then usage() end 651 bcsave(ctx, arg[1], arg[2]) 652 end 653 end 654 655 ------------------------------------------------------------------------------ 656 657 -- Public module functions. 658 return { 659 start = docmd -- Process -b command line option. 660 } 661