seccomp-tools 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa755fd2e7ed97279b094dfa28bd668aef4ee638005f0b38405066502fc8a30d
4
- data.tar.gz: 56b0d51796d97a89f67262c36716833d078ce19ea6b58fbb83c2f8bee88b8685
3
+ metadata.gz: 898cc2a23df6443f39054c4b9027a576fb929729f74fc804ae82ae3b11c6772e
4
+ data.tar.gz: 0e07d2d1914ac7185135425d0e34f0dcb1b6100e977176d8bd9ce23f95a3f287
5
5
  SHA512:
6
- metadata.gz: 027f57a717cb63fadeefd4a60213115a27ed51bb1eb3e6b58251690ed7c1d42335086ff4448b480296b77d395ba2e8718fa662e227394abfddaf80b19b769b74
7
- data.tar.gz: f6aca254b4a43d6c60d5df7e6694b976de1e56fbb6d6585bd46fd17c0849820d76c81f25bbc872dc785576e05eeaaab5d0276a3f065b730988973c5073ace761
6
+ metadata.gz: ec604fca33e16dd6ca77e923e34629338245828abdf3c97e9974d205a2306aa9ae0f3bc074b90e056790a8de86ed6484ab17ce190524b30394f2fda2b5dd9432
7
+ data.tar.gz: 399e5e77fa99de23f3ad5013c442fc29a800a261d04150ab0bd467744a9302601ca7c60e5570e4e65f423cf4c93e4eb4428ae3f33b97d6a64c223af13b342e47
data/README.md CHANGED
@@ -1,26 +1,26 @@
1
1
  [![Build Status](https://github.com/david942j/seccomp-tools/workflows/build/badge.svg)](https://github.com/david942j/seccomp-tools/actions)
2
- [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=david942j/seccomp-tools)](https://dependabot.com)
3
2
  [![Code Climate](https://codeclimate.com/github/david942j/seccomp-tools/badges/gpa.svg)](https://codeclimate.com/github/david942j/seccomp-tools)
4
3
  [![Issue Count](https://codeclimate.com/github/david942j/seccomp-tools/badges/issue_count.svg)](https://codeclimate.com/github/david942j/seccomp-tools)
5
4
  [![Test Coverage](https://codeclimate.com/github/david942j/seccomp-tools/badges/coverage.svg)](https://codeclimate.com/github/david942j/seccomp-tools/coverage)
6
5
  [![Inline docs](https://inch-ci.org/github/david942j/seccomp-tools.svg?branch=master)](https://inch-ci.org/github/david942j/seccomp-tools)
6
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/david942j/seccomp-tools/)
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
8
 
9
9
  # Seccomp Tools
10
10
  Provide powerful tools for seccomp analysis.
11
11
 
12
- This project is targeted to (but not limited to) analyze seccomp sandbox in CTF pwn challenges.
13
- Some features might be CTF-specific, but still useful for analyzing seccomp in real-case.
12
+ This project targets to (but is not limited to) analyze seccomp sandbox in CTF pwn challenges.
13
+ Some features might be CTF-specific, but also useful for analyzing seccomp of real cases.
14
14
 
15
15
  ## Features
16
- * Dump - Automatically dumps seccomp-bpf from execution file(s).
17
- * Disasm - Converts bpf to human readable format.
18
- - Simple decompile.
19
- - Display syscall names and arguments when possible.
16
+ * Dump - Automatically dumps seccomp BPF from execution file(s).
17
+ * Disasm - Converts seccomp BPF to a human readable format.
18
+ - With simple decompilation.
19
+ - With syscall names and arguments whenever possible.
20
20
  - Colorful!
21
- * Asm - Write seccomp rules is so easy!
21
+ * Asm - Makes writing seccomp rules similar to writing codes.
22
22
  * Emu - Emulates seccomp rules.
23
- * Supports multi-architectures.
23
+ * Supports multi-architecture.
24
24
 
25
25
  ## Installation
26
26
 
@@ -76,8 +76,8 @@ $ seccomp-tools dump --help
76
76
 
77
77
  ### dump
78
78
 
79
- Dumps the seccomp bpf from an execution file.
80
- This work is done by the `ptrace` syscall.
79
+ Dumps the seccomp BPF from an execution file.
80
+ This work is done by utilizing the `ptrace` syscall.
81
81
 
82
82
  NOTICE: beware of the execution file will be executed.
83
83
  ```bash
@@ -124,7 +124,7 @@ $ seccomp-tools dump spec/binary/twctf-2016-diary -f raw | xxd
124
124
 
125
125
  ### disasm
126
126
 
127
- Disassembles the seccomp from raw bpf.
127
+ Disassembles the seccomp from raw BPF.
128
128
  ```bash
129
129
  $ xxd spec/data/twctf-2016-diary.bpf | head -n 3
130
130
  # 00000000: 2000 0000 0000 0000 1500 0001 0200 0000 ...............
@@ -170,7 +170,7 @@ $ seccomp-tools asm
170
170
  # -f, --format FORMAT Output format. FORMAT can only be one of <inspect|raw|c_array|c_source|assembly>.
171
171
  # Default: inspect
172
172
  # -a, --arch ARCH Specify architecture.
173
- # Supported architectures are <aarch64|amd64|i386>.
173
+ # Supported architectures are <aarch64|amd64|i386|s390x>.
174
174
  # Default: amd64
175
175
 
176
176
  # Input file for asm
@@ -259,6 +259,83 @@ $ seccomp-tools asm spec/data/libseccomp.asm -f raw | seccomp-tools disasm -
259
259
 
260
260
  ```
261
261
 
262
+ Since v1.6.0 [not released yet], `asm` has switched to using a yacc-based syntax parser, hence supports more flexible and intuitive syntax!
263
+
264
+ ```bash
265
+ $ cat spec/data/example.asm
266
+ # # An example of supported assembly syntax
267
+ # if (A == X)
268
+ # goto next # 'next' is a reserved label, means the next statement ("A = args[0]" in this example)
269
+ # else
270
+ # goto err_label # custom defined label
271
+ # A = args[0]
272
+ # if (
273
+ # A # put a comment here is also valid
274
+ # == 0x123
275
+ # ) goto disallow
276
+ # if (! (A & 0x1337)) # support bang in if-conditions
277
+ # goto 0 # equivalent to 'goto next'
278
+ # else goto 2 # goto $ + 2, 'mem[0] = A' in this example
279
+ # A = sys_number
280
+ # A = instruction_pointer >> 32
281
+ # mem[0] = A
282
+ # A = data[4] # equivalent to 'A = arch'
283
+ # err_label: return ERRNO(1337)
284
+ # disallow:
285
+ # return KILL
286
+
287
+ $ seccomp-tools asm spec/data/example.asm -f raw | seccomp-tools disasm -
288
+ # line CODE JT JF K
289
+ # =================================
290
+ # 0000: 0x1d 0x00 0x07 0x00000000 if (A != X) goto 0008
291
+ # 0001: 0x20 0x00 0x00 0x00000010 A = args[0]
292
+ # 0002: 0x15 0x06 0x00 0x00000123 if (A == 0x123) goto 0009
293
+ # 0003: 0x45 0x02 0x00 0x00001337 if (A & 0x1337) goto 0006
294
+ # 0004: 0x20 0x00 0x00 0x00000000 A = sys_number
295
+ # 0005: 0x20 0x00 0x00 0x0000000c A = instruction_pointer >> 32
296
+ # 0006: 0x02 0x00 0x00 0x00000000 mem[0] = A
297
+ # 0007: 0x20 0x00 0x00 0x00000004 A = arch
298
+ # 0008: 0x06 0x00 0x00 0x00050539 return ERRNO(1337)
299
+ # 0009: 0x06 0x00 0x00 0x00000000 return KILL
300
+
301
+ ```
302
+
303
+ The output of `seccomp-tools disasm <file> --asm-able` is a valid input of `asm`:
304
+ ```bash
305
+ $ seccomp-tools disasm spec/data/x32.bpf --asm-able
306
+ # 0000: A = arch
307
+ # 0001: if (A != ARCH_X86_64) goto 0011
308
+ # 0002: A = sys_number
309
+ # 0003: if (A < 0x40000000) goto 0011
310
+ # 0004: if (A == x32_read) goto 0011
311
+ # 0005: if (A == x32_write) goto 0011
312
+ # 0006: if (A == x32_iopl) goto 0011
313
+ # 0007: if (A != x32_mmap) goto 0011
314
+ # 0008: A = args[0]
315
+ # 0009: if (A == 0x0) goto 0011
316
+ # 0010: return ERRNO(5)
317
+ # 0011: return ALLOW
318
+
319
+
320
+ # disasm then asm then disasm!
321
+ $ seccomp-tools disasm spec/data/x32.bpf --asm-able | seccomp-tools asm - -f raw | seccomp-tools disasm -
322
+ # line CODE JT JF K
323
+ # =================================
324
+ # 0000: 0x20 0x00 0x00 0x00000004 A = arch
325
+ # 0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
326
+ # 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
327
+ # 0003: 0x35 0x00 0x07 0x40000000 if (A < 0x40000000) goto 0011
328
+ # 0004: 0x15 0x06 0x00 0x40000000 if (A == x32_read) goto 0011
329
+ # 0005: 0x15 0x05 0x00 0x40000001 if (A == x32_write) goto 0011
330
+ # 0006: 0x15 0x04 0x00 0x400000ac if (A == x32_iopl) goto 0011
331
+ # 0007: 0x15 0x00 0x03 0x40000009 if (A != x32_mmap) goto 0011
332
+ # 0008: 0x20 0x00 0x00 0x00000010 A = addr # x32_mmap(addr, len, prot, flags, fd, pgoff)
333
+ # 0009: 0x15 0x01 0x00 0x00000000 if (A == 0x0) goto 0011
334
+ # 0010: 0x06 0x00 0x00 0x00050005 return ERRNO(5)
335
+ # 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
336
+
337
+ ```
338
+
262
339
  ### Emu
263
340
 
264
341
  Emulates seccomp given `sys_nr`, `arg0`, `arg1`, etc.
@@ -268,7 +345,7 @@ $ seccomp-tools emu --help
268
345
  #
269
346
  # Usage: seccomp-tools emu [options] BPF_FILE [sys_nr [arg0 [arg1 ... arg5]]]
270
347
  # -a, --arch ARCH Specify architecture.
271
- # Supported architectures are <aarch64|amd64|i386>.
348
+ # Supported architectures are <aarch64|amd64|i386|s390x>.
272
349
  # Default: amd64
273
350
  # -q, --[no-]quiet Run quietly, only show emulation result.
274
351
 
@@ -301,12 +378,15 @@ $ seccomp-tools emu spec/data/libseccomp.bpf write 0x3
301
378
 
302
379
  ![emu](https://github.com/david942j/seccomp-tools/blob/master/examples/emu-amigo.png?raw=true)
303
380
 
304
- ## Architecture Supported
381
+ ## Supported Architectures
305
382
 
306
383
  - [x] x86_64
307
384
  - [x] x32
308
385
  - [x] x86
309
- - [x] arm64 (Thanks to @saagarjha!)
386
+ - [x] arm64 (@saagarjha)
387
+ - [x] s390x (@iii-i)
388
+
389
+ Pull Requests of adding more architectures support are welcome!
310
390
 
311
391
  ## Development
312
392
 
@@ -327,6 +407,6 @@ I recommend to use [rbenv](https://github.com/rbenv/rbenv) for your Ruby environ
327
407
 
328
408
  ## I Need You
329
409
 
330
- Any suggestion or feature request is welcome!
331
- Feel free to file an issue or send a pull request.
410
+ Any suggestions or feature requests are welcome!
411
+ Feel free to file issues or send pull requests.
332
412
  And, if you like this work, I'll be happy to be [starred](https://github.com/david942j/seccomp-tools/stargazers) :grimacing:
@@ -10,16 +10,18 @@ module SeccompTools
10
10
 
11
11
  # Assembler of seccomp bpf.
12
12
  # @param [String] str
13
- # @param [:amd64, :i386] arch
13
+ # @param [String] filename
14
+ # Only used for error messages.
15
+ # @param [Symbol?] arch
14
16
  # @return [String]
15
- # Raw bpf bytes.
17
+ # Raw BPF bytes.
16
18
  # @example
17
19
  # SeccompTools::Asm.asm(<<-EOS)
18
20
  # # lines start with '#' are comments
19
21
  # A = sys_number # here's a comment, too
20
22
  # A >= 0x40000000 ? dead : next # 'next' is a keyword, denote the next instruction
21
23
  # A == read ? ok : next # custom defined label 'dead' and 'ok'
22
- # A == 1 ? ok : next # SYS_write = 1 in amd64
24
+ # A == 1 ? ok : next # SYS_write = 1 on amd64
23
25
  # return ERRNO(1)
24
26
  # dead:
25
27
  # return KILL
@@ -27,10 +29,10 @@ module SeccompTools
27
29
  # return ALLOW
28
30
  # EOS
29
31
  # #=> <raw binary bytes>
30
- def asm(str, arch: nil)
32
+ def asm(str, filename: '-', arch: nil)
33
+ filename = nil if filename == '-'
31
34
  arch = Util.system_arch if arch.nil?
32
- compiler = Compiler.new(arch)
33
- str.lines.each { |l| compiler.process(l) }
35
+ compiler = Compiler.new(str, filename, arch)
34
36
  compiler.compile!.map(&:asm).join
35
37
  end
36
38
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'seccomp-tools/asm/tokenizer'
3
+ require 'seccomp-tools/asm/sasm.tab'
4
+ require 'seccomp-tools/asm/scalar'
5
+ require 'seccomp-tools/asm/scanner'
4
6
  require 'seccomp-tools/bpf'
5
- require 'seccomp-tools/const'
7
+ require 'seccomp-tools/error'
6
8
 
7
9
  module SeccompTools
8
10
  module Asm
@@ -12,74 +14,99 @@ module SeccompTools
12
14
  class Compiler
13
15
  # Instantiate a {Compiler} object.
14
16
  #
17
+ # @param [String] source
18
+ # Input string.
19
+ # @param [String?] filename
20
+ # Only used in error messages.
15
21
  # @param [Symbol] arch
16
22
  # Architecture.
17
- def initialize(arch)
23
+ def initialize(source, filename, arch)
24
+ @scanner = Scanner.new(source, arch, filename: filename)
18
25
  @arch = arch
19
- @insts = []
20
- @labels = {}
21
- @insts_linenum = {}
22
- @input = []
23
- end
24
-
25
- # Before compile assembly codes, process each lines.
26
- #
27
- # With this we can support label in seccomp rules.
28
- # @param [String] line
29
- # One line of seccomp rule.
30
- # @return [void]
31
- def process(line)
32
- @input << line.strip
33
- line = remove_comment(line)
34
- @token = Tokenizer.new(line)
35
- return if line.empty?
36
-
37
- begin
38
- res = case line
39
- when /\?/ then cmp
40
- when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
41
- when /^return/ then ret
42
- when /^A\s*=\s*-A/ then alu_neg
43
- when /^(A|X)\s*=[^=]/ then assign
44
- when /^mem\[\d+\]\s*=\s*(A|X)/ then store
45
- when /^A\s*.{1,2}=/ then alu
46
- when /^(goto|jmp|jump)/ then jmp_abs
47
- end
48
- rescue ArgumentError => e
49
- invalid(@input.size - 1, e.message)
50
- end
51
- invalid(@input.size - 1) if res.nil?
52
- if res.first == :label
53
- @labels[res.last] = @insts.size
54
- else
55
- @insts << res
56
- @insts_linenum[@insts.size - 1] = @input.size - 1
57
- end
26
+ @symbols = {}
58
27
  end
59
28
 
60
29
  # Compiles the processed instructions.
61
30
  #
62
31
  # @return [Array<SeccompTools::BPF>]
63
32
  # Returns the compiled {BPF} array.
64
- # @raise [ArgumentError]
33
+ # @raise [SeccompTools::Error]
65
34
  # Raises the error found during compilation.
66
35
  def compile!
67
- @insts.map.with_index do |inst, idx|
36
+ @scanner.validate!
37
+ statements = SeccompAsmParser.new(@scanner).parse
38
+ fixup_symbols(statements)
39
+ resolve_symbols(statements)
40
+
41
+ statements.map.with_index do |s, idx|
68
42
  @line = idx
69
- case inst.first
70
- when :assign then compile_assign(inst[1], inst[2])
71
- when :alu then compile_alu(inst[1], inst[2])
72
- when :ret then compile_ret(inst[1])
73
- when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
74
- when :jmp_abs then compile_jmp_abs(inst[1])
43
+ case s.type
44
+ when :alu then emit_alu(*s.data)
45
+ when :assign then emit_assign(*s.data)
46
+ when :if then emit_cmp(*s.data)
47
+ when :ret then emit_ret(*s.data)
75
48
  end
76
49
  end
77
- rescue ArgumentError => e
78
- invalid(@insts_linenum[@line], e.message)
79
50
  end
80
51
 
81
52
  private
82
53
 
54
+ def fixup_symbols(statements)
55
+ statements.each_with_index do |statement, idx|
56
+ statement.symbols.uniq(&:str).each do |s|
57
+ if @symbols[s.str]
58
+ msg = @scanner.format_error(s, "duplicate label '#{s.str}'")
59
+ msg += @scanner.format_error(@symbols[s.str][0], 'previously defined here')
60
+ raise SeccompTools::DuplicateLabelError, msg
61
+ end
62
+
63
+ @symbols[s.str] = [s, idx]
64
+ end
65
+ end
66
+ end
67
+
68
+ def resolve_symbols(statements)
69
+ statements.each_with_index do |statement, idx|
70
+ next if statement.type != :if
71
+
72
+ jt = resolve_symbol(idx, statement.data[1])
73
+ jf = resolve_symbol(idx, statement.data[2])
74
+ statement.data[1] = jt
75
+ statement.data[2] = jf
76
+ end
77
+ end
78
+
79
+ # The farthest distance of a relative jump in BPF.
80
+ JUMP_DISTANCE_MAX = 255
81
+
82
+ # @param [Integer] index
83
+ # @param [SeccompTools::Asm::Token, :next] sym
84
+ def resolve_symbol(index, sym)
85
+ return 0 if sym.is_a?(Symbol) && sym == :next
86
+
87
+ str = sym.str
88
+ return 0 if str == 'next'
89
+
90
+ if @symbols[str].nil?
91
+ # special case - goto <n> can be considered as $+1+<n>
92
+ return str.to_i if str == str.to_i.to_s && str.to_i <= JUMP_DISTANCE_MAX
93
+
94
+ raise SeccompTools::UndefinedLabelError,
95
+ @scanner.format_error(sym, "Cannot find label '#{str}'")
96
+ end
97
+
98
+ (@symbols[str][1] - index - 1).tap do |dis|
99
+ if dis.negative?
100
+ raise SeccompTools::BackwardJumpError,
101
+ @scanner.format_error(sym, "Does not support backward jumping to '#{str}'")
102
+ end
103
+ if dis > JUMP_DISTANCE_MAX
104
+ raise SeccompTools::LongJumpError,
105
+ @scanner.format_error(sym, "Does not support jumping farther than #{JUMP_DISTANCE_MAX}, got: #{dis}")
106
+ end
107
+ end
108
+ end
109
+
83
110
  # Emits a raw BPF object.
84
111
  #
85
112
  # @return [BPF]
@@ -98,201 +125,80 @@ module SeccompTools
98
125
  BPF.new({ code: code, k: k, jt: jt, jf: jf }, @arch, @line)
99
126
  end
100
127
 
128
+ # A = -A
101
129
  # A = X / X = A
102
130
  # mem[i] = <A|X>
103
- # <A|X> = 123|sys_const
104
131
  # A = len
105
- # <A|X> = mem[i]
106
- # A = args_h[i]|args[i]|sys_number|arch
107
- # A = data[4 * i]
108
- def compile_assign(dst, src)
132
+ # <A|X> = <immi|mem[i]|data[i]>
133
+ def emit_assign(dst, src)
134
+ return emit(:alu, :neg) if src.is_a?(Symbol) && src == :neg
109
135
  # misc txa / tax
110
- return compile_assign_misc(dst, src) if (dst == :a && src == :x) || (dst == :x && src == :a)
136
+ return emit(:misc, dst.a? ? :txa : :tax) if (dst.a? && src.x?) || (dst.x? && src.a?)
111
137
  # case of st / stx
112
- return emit(src == :x ? :stx : :st, k: dst.last) if dst.is_a?(Array) && dst.first == :mem
113
-
114
- src = evaluate(src)
115
- ld = dst == :x ? :ldx : :ld
116
- # <A|X> = <immi>
117
- return emit(ld, :imm, k: src) if src.is_a?(Integer)
118
- # now src must be in form [:len/:mem/:data, num]
119
- return emit(ld, src.first, k: src.last) if src.first == :mem || src.first == :len
120
- # check if num is multiple of 4
121
- raise ArgumentError, 'Index of data[] must be a multiple of 4' if src.last % 4 != 0
138
+ return emit(src.x? ? :stx : :st, k: dst.val) if dst.mem?
122
139
 
123
- emit(ld, :abs, k: src.last)
140
+ emit_ld(dst, src)
124
141
  end
125
142
 
126
- def compile_assign_misc(dst, _src)
127
- emit(:misc, dst == :a ? :txa : :tax)
143
+ def emit_ld(dst, src)
144
+ ld = dst.x? ? :ldx : :ld
145
+ return emit(ld, :len, k: 0) if src.len?
146
+ return emit(ld, :imm, k: src.to_i) if src.const?
147
+ return emit(ld, :mem, k: src.val) if src.mem?
148
+
149
+ emit(ld, :abs, k: src.val) if src.data?
128
150
  end
129
151
 
130
- def compile_alu(op, val)
131
- return emit(:alu, :neg) if op == :neg
152
+ def emit_alu(op, val)
153
+ src, k = val.x? ? [:x, 0] : [:k, val.to_i]
154
+ emit(:alu, convert_alu_op(op), src, k: k)
155
+ end
132
156
 
133
- val = evaluate(val)
134
- src = val == :x ? :x : :k
135
- val = 0 if val == :x
136
- emit(:alu, op, src, k: val)
157
+ def convert_alu_op(op)
158
+ {
159
+ '+' => :add,
160
+ '-' => :sub,
161
+ '*' => :mul,
162
+ '/' => :div,
163
+ '|' => :or,
164
+ '&' => :and,
165
+ '<<' => :lsh,
166
+ '>>' => :rsh,
167
+ '^' => :xor
168
+ }[op[0..-2]]
137
169
  end
138
170
 
139
- def compile_ret(val)
140
- if val == :a
171
+ def emit_ret(val)
172
+ if val.a?
141
173
  src = :a
142
174
  val = 0
143
175
  end
144
- emit(:ret, src, k: val)
145
- end
146
-
147
- def compile_jmp_abs(target)
148
- targ = label_offset(target)
149
- emit(:jmp, :ja, k: targ)
150
- end
151
-
152
- # Compiles comparison.
153
- def compile_cmp(op, val, jt, jf)
154
- jt = label_offset(jt)
155
- jf = label_offset(jf)
156
- val = evaluate(val)
157
- src = val == :x ? :x : :k
158
- val = 0 if val == :x
159
- emit(:jmp, op, src, jt: jt, jf: jf, k: val)
176
+ emit(:ret, src, k: val.to_i)
160
177
  end
161
178
 
162
- def label_offset(label)
163
- return label if label.is_a?(Integer)
164
- return 0 if label == 'next'
165
- raise ArgumentError, "Undefined label #{label.inspect}" if @labels[label].nil?
166
- raise ArgumentError, "Does not support backward jumping to #{label.inspect}" if @labels[label] < @line
179
+ def emit_cmp(cmp, jt, jf)
180
+ jop, jt, jf = convert_jmp_op(cmp, jt, jf)
181
+ return emit(:jmp, jop, 0, jt: 0, jf: 0, k: jt) if jop == :ja || jt == jf
167
182
 
168
- @labels[label] - @line - 1
183
+ val = cmp[1]
184
+ src = val.x? ? :x : :k
185
+ k = val.x? ? 0 : val.to_i
186
+ emit(:jmp, jop, src, jt: jt, jf: jf, k: k)
169
187
  end
170
188
 
171
- def evaluate(val)
172
- return val if val.is_a?(Integer) || val == :x || val == :a
173
-
174
- # keywords
175
- val = case val
176
- when 'sys_number' then [:data, 0]
177
- when 'arch' then [:data, 4]
178
- when 'len' then [:len, 0]
179
- else val
180
- end
181
- return eval_constants(val) if val.is_a?(String)
182
-
183
- # remains are [:mem/:data/:args/:args_h, <num>]
184
- # first convert args to data
185
- val = [:data, val.last * 8 + 16] if val.first == :args
186
- val = [:data, val.last * 8 + 20] if val.first == :args_h
187
- val
188
- end
189
+ # == != >= <= > < &
190
+ def convert_jmp_op(cmp, jt, jf)
191
+ return [:ja, jt, jf] if cmp.nil?
189
192
 
190
- def eval_constants(str)
191
- Const::Syscall.const_get(@arch.upcase.to_sym)[str.to_sym] ||
192
- Const::Audit::ARCH[str] ||
193
- raise(ArgumentError, "Invalid constant: #{str.inspect}")
194
- end
195
-
196
- attr_reader :token
197
-
198
- # <goto|jmp|jump> <label|Integer>
199
- def jmp_abs
200
- token.fetch('goto') ||
201
- token.fetch('jmp') ||
202
- token.fetch('jump') ||
203
- raise(ArgumentError, "Invalid jump alias: #{token.cur.inspect}")
204
- target = token.fetch!(:goto)
205
- [:jmp_abs, target]
206
- end
207
-
208
- # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
209
- def cmp
210
- token.fetch!('A')
211
- op = token.fetch!(:comparison)
212
- dst = token.fetch!(:sys_num_x)
213
- token.fetch!('?')
214
- jt = token.fetch!(:goto)
215
- token.fetch!(':')
216
- jf = token.fetch!(:goto)
217
- convert = {
218
- :< => :>=,
219
- :<= => :>,
220
- :!= => :==
221
- }
222
- if convert[op]
223
- op = convert[op]
224
- jt, jf = jf, jt
193
+ case cmp[0]
194
+ when '==' then [:jeq, jt, jf]
195
+ when '!=' then [:jeq, jf, jt]
196
+ when '>=' then [:jge, jt, jf]
197
+ when '<=' then [:jgt, jf, jt]
198
+ when '>' then [:jgt, jt, jf]
199
+ when '<' then [:jge, jf, jt]
200
+ when '&' then [:jset, jt, jf]
225
201
  end
226
- op = {
227
- :>= => :jge,
228
- :> => :jgt,
229
- :== => :jeq
230
- }[op]
231
- [:cmp, op, dst, jt, jf]
232
- end
233
-
234
- def ret
235
- token.fetch!('return')
236
- [:ret, token.fetch!(:ret)]
237
- end
238
-
239
- # possible types after '=':
240
- # A = X
241
- # X = A
242
- # A = 123
243
- # A = data[i]
244
- # A = mem[i]
245
- # A = args[i]
246
- # A = sys_number|arch
247
- # A = len
248
- def assign
249
- dst = token.fetch!(:ax)
250
- token.fetch!('=')
251
- src = token.fetch(:ax) ||
252
- token.fetch(:sys_num_x) ||
253
- token.fetch(:ary) ||
254
- token.fetch('sys_number') ||
255
- token.fetch('arch') ||
256
- token.fetch('len') ||
257
- raise(ArgumentError, "Invalid source: #{token.cur.inspect}")
258
- [:assign, dst, src]
259
- end
260
-
261
- # returns same format as assign
262
- def store
263
- [:assign, token.fetch!(:ary), token.fetch!('=') && token.fetch!(:ax)]
264
- end
265
-
266
- def define_label
267
- name = token.fetch!(:goto)
268
- token.fetch(':')
269
- [:label, name]
270
- end
271
-
272
- # A op= sys_num_x
273
- def alu
274
- token.fetch!('A')
275
- op = token.fetch!(:alu_op)
276
- token.fetch!('=')
277
- src = token.fetch!(:sys_num_x)
278
- [:alu, op, src]
279
- end
280
-
281
- # A = -A
282
- def alu_neg
283
- %i[alu neg]
284
- end
285
-
286
- def remove_comment(line)
287
- line = line.to_s.dup
288
- line.slice!(/#.*\Z/m)
289
- line.strip
290
- end
291
-
292
- def invalid(line, extra_msg = nil)
293
- message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
294
- message += "\nError: #{extra_msg}" if extra_msg
295
- raise ArgumentError, message
296
202
  end
297
203
  end
298
204
  end