ljx

FORK: LuaJIT with native 5.2 and 5.3 support
git clone https://git.neptards.moe/neptards/ljx.git
Log | Files | Refs | README

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