Subversion Repositories eduke32

Rev

Rev 4001 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4001 helixhorne 1
----------------------------------------------------------------------------
2
-- LuaJIT module to save/list bytecode.
3
--
4412 helixhorne 4
-- Copyright (C) 2005-2014 Mike Pall. All rights reserved.
4001 helixhorne 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")
4412 helixhorne 14
assert(jit.version_num == 20003, "LuaJIT core/library version mismatch")
4001 helixhorne 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, ppc = true, ppcspe = 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, 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" then
204
    is64 = true
205
  elseif ctx.arch == "ppc" or ctx.arch == "ppcspe" 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, ppc=20, ppcspe=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[4];
481
  struct {
482
    mach_header hdr;
483
    mach_segment_command seg;
484
    mach_section sec;
485
    mach_symtab_command sym;
486
  } arch[4];
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
  else
498
    check(ctx.arch == "x86", "unsupported architecture for OSX")
499
  end
500
  local function aligned(v, a) return bit.band(v+a-1, -a) end
501
  local be32 = bit.bswap -- Mach-O FAT is BE, supported archs are LE.
502
 
503
  -- Create Mach-O object and fill in header.
504
  local o = ffi.new(mobj)
505
  local mach_size = aligned(ffi.offsetof(o, "space")+#symname+2, align)
506
  local cputype = ({ x86={7}, x64={0x01000007}, arm={7,12,12,12} })[ctx.arch]
507
  local cpusubtype = ({ x86={3}, x64={3}, arm={3,6,9,11} })[ctx.arch]
508
  if isfat then
509
    o.fat.magic = be32(0xcafebabe)
510
    o.fat.nfat_arch = be32(#cpusubtype)
511
  end
512
 
513
  -- Fill in sections and symbols.
514
  for i=0,#cpusubtype-1 do
515
    local ofs = 0
516
    if isfat then
517
      local a = o.fat_arch[i]
518
      a.cputype = be32(cputype[i+1])
519
      a.cpusubtype = be32(cpusubtype[i+1])
520
      -- Subsequent slices overlap each other to share data.
521
      ofs = ffi.offsetof(o, "arch") + i*ffi.sizeof(o.arch[0])
522
      a.offset = be32(ofs)
523
      a.size = be32(mach_size-ofs+#s)
524
    end
525
    local a = o.arch[i]
526
    a.hdr.magic = is64 and 0xfeedfacf or 0xfeedface
527
    a.hdr.cputype = cputype[i+1]
528
    a.hdr.cpusubtype = cpusubtype[i+1]
529
    a.hdr.filetype = 1
530
    a.hdr.ncmds = 2
531
    a.hdr.sizeofcmds = ffi.sizeof(a.seg)+ffi.sizeof(a.sec)+ffi.sizeof(a.sym)
532
    a.seg.cmd = is64 and 0x19 or 0x1
533
    a.seg.cmdsize = ffi.sizeof(a.seg)+ffi.sizeof(a.sec)
534
    a.seg.vmsize = #s
535
    a.seg.fileoff = mach_size-ofs
536
    a.seg.filesize = #s
537
    a.seg.maxprot = 1
538
    a.seg.initprot = 1
539
    a.seg.nsects = 1
540
    ffi.copy(a.sec.sectname, "__data")
541
    ffi.copy(a.sec.segname, "__DATA")
542
    a.sec.size = #s
543
    a.sec.offset = mach_size-ofs
544
    a.sym.cmd = 2
545
    a.sym.cmdsize = ffi.sizeof(a.sym)
546
    a.sym.symoff = ffi.offsetof(o, "sym_entry")-ofs
547
    a.sym.nsyms = 1
548
    a.sym.stroff = ffi.offsetof(o, "sym_entry")+ffi.sizeof(o.sym_entry)-ofs
549
    a.sym.strsize = aligned(#symname+2, align)
550
  end
551
  o.sym_entry.type = 0xf
552
  o.sym_entry.sect = 1
553
  o.sym_entry.strx = 1
554
  ffi.copy(o.space+1, symname)
555
 
556
  -- Write Macho-O object file.
557
  local fp = savefile(output, "wb")
558
  fp:write(ffi.string(o, mach_size))
559
  bcsave_tail(fp, output, s)
560
end
561
 
562
local function bcsave_obj(ctx, output, s)
563
  local ok, ffi = pcall(require, "ffi")
564
  check(ok, "FFI library required to write this file type")
565
  if ctx.os == "windows" then
566
    return bcsave_peobj(ctx, output, s, ffi)
567
  elseif ctx.os == "osx" then
568
    return bcsave_machobj(ctx, output, s, ffi)
569
  else
570
    return bcsave_elfobj(ctx, output, s, ffi)
571
  end
572
end
573
 
574
------------------------------------------------------------------------------
575
 
576
local function bclist(input, output)
577
  local f = readfile(input)
578
  require("jit.bc").dump(f, savefile(output, "w"), true)
579
end
580
 
581
local function bcsave(ctx, input, output)
582
  local f = readfile(input)
583
  local s = string.dump(f, ctx.strip)
584
  local t = ctx.type
585
  if not t then
586
    t = detecttype(output)
587
    ctx.type = t
588
  end
589
  if t == "raw" then
590
    bcsave_raw(output, s)
591
  else
592
    if not ctx.modname then ctx.modname = detectmodname(input) end
593
    if t == "obj" then
594
      bcsave_obj(ctx, output, s)
595
    else
596
      bcsave_c(ctx, output, s)
597
    end
598
  end
599
end
600
 
601
local function docmd(...)
602
  local arg = {...}
603
  local n = 1
604
  local list = false
605
  local ctx = {
606
    strip = true, arch = jit.arch, os = string.lower(jit.os),
607
    type = false, modname = false,
608
  }
609
  while n <= #arg do
610
    local a = arg[n]
611
    if type(a) == "string" and string.sub(a, 1, 1) == "-" and a ~= "-" then
612
      table.remove(arg, n)
613
      if a == "--" then break end
614
      for m=2,#a do
615
        local opt = string.sub(a, m, m)
616
        if opt == "l" then
617
          list = true
618
        elseif opt == "s" then
619
          ctx.strip = true
620
        elseif opt == "g" then
621
          ctx.strip = false
622
        else
623
          if arg[n] == nil or m ~= #a then usage() end
624
          if opt == "e" then
625
            if n ~= 1 then usage() end
626
            arg[1] = check(loadstring(arg[1]))
627
          elseif opt == "n" then
628
            ctx.modname = checkmodname(table.remove(arg, n))
629
          elseif opt == "t" then
630
            ctx.type = checkarg(table.remove(arg, n), map_type, "file type")
631
          elseif opt == "a" then
632
            ctx.arch = checkarg(table.remove(arg, n), map_arch, "architecture")
633
          elseif opt == "o" then
634
            ctx.os = checkarg(table.remove(arg, n), map_os, "OS name")
635
          else
636
            usage()
637
          end
638
        end
639
      end
640
    else
641
      n = n + 1
642
    end
643
  end
644
  if list then
645
    if #arg == 0 or #arg > 2 then usage() end
646
    bclist(arg[1], arg[2] or "-")
647
  else
648
    if #arg ~= 2 then usage() end
649
    bcsave(ctx, arg[1], arg[2])
650
  end
651
end
652
 
653
------------------------------------------------------------------------------
654
 
655
-- Public module functions.
656
module(...)
657
 
658
start = docmd -- Process -b command line option.
659