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 +4 -4
- data/README.md +98 -18
- data/lib/seccomp-tools/asm/asm.rb +8 -6
- data/lib/seccomp-tools/asm/compiler.rb +130 -224
- data/lib/seccomp-tools/asm/sasm.tab.rb +780 -0
- data/lib/seccomp-tools/asm/sasm.y +175 -0
- data/lib/seccomp-tools/asm/scalar.rb +129 -0
- data/lib/seccomp-tools/asm/scanner.rb +163 -0
- data/lib/seccomp-tools/asm/statement.rb +32 -0
- data/lib/seccomp-tools/asm/token.rb +29 -0
- data/lib/seccomp-tools/bpf.rb +31 -7
- data/lib/seccomp-tools/cli/asm.rb +1 -1
- data/lib/seccomp-tools/cli/base.rb +4 -4
- data/lib/seccomp-tools/cli/disasm.rb +26 -2
- data/lib/seccomp-tools/const.rb +25 -4
- data/lib/seccomp-tools/consts/sys_nr/amd64.rb +1 -0
- data/lib/seccomp-tools/consts/sys_nr/s390x.rb +365 -0
- data/lib/seccomp-tools/disasm/context.rb +2 -2
- data/lib/seccomp-tools/disasm/disasm.rb +15 -9
- data/lib/seccomp-tools/dumper.rb +1 -2
- data/lib/seccomp-tools/emulator.rb +5 -10
- data/lib/seccomp-tools/error.rb +31 -0
- data/lib/seccomp-tools/instruction/base.rb +1 -1
- data/lib/seccomp-tools/instruction/jmp.rb +14 -1
- data/lib/seccomp-tools/instruction/ld.rb +3 -1
- data/lib/seccomp-tools/syscall.rb +13 -8
- data/lib/seccomp-tools/templates/asm.s390x.asm +26 -0
- data/lib/seccomp-tools/util.rb +2 -1
- data/lib/seccomp-tools/version.rb +1 -1
- metadata +17 -15
- data/lib/seccomp-tools/asm/tokenizer.rb +0 -169
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 898cc2a23df6443f39054c4b9027a576fb929729f74fc804ae82ae3b11c6772e
|
4
|
+
data.tar.gz: 0e07d2d1914ac7185135425d0e34f0dcb1b6100e977176d8bd9ce23f95a3f287
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
13
|
-
Some features might be CTF-specific, but
|
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
|
17
|
-
* Disasm - Converts
|
18
|
-
-
|
19
|
-
-
|
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 -
|
21
|
+
* Asm - Makes writing seccomp rules similar to writing codes.
|
22
22
|
* Emu - Emulates seccomp rules.
|
23
|
-
* Supports multi-
|
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
|
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
|
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
|
-
##
|
381
|
+
## Supported Architectures
|
305
382
|
|
306
383
|
- [x] x86_64
|
307
384
|
- [x] x32
|
308
385
|
- [x] x86
|
309
|
-
- [x] arm64 (
|
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
|
331
|
-
Feel free to file
|
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 [
|
13
|
+
# @param [String] filename
|
14
|
+
# Only used for error messages.
|
15
|
+
# @param [Symbol?] arch
|
14
16
|
# @return [String]
|
15
|
-
# Raw
|
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
|
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/
|
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/
|
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
|
-
@
|
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 [
|
33
|
+
# @raise [SeccompTools::Error]
|
65
34
|
# Raises the error found during compilation.
|
66
35
|
def compile!
|
67
|
-
@
|
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
|
70
|
-
when :
|
71
|
-
when :
|
72
|
-
when :
|
73
|
-
when :
|
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
|
-
|
107
|
-
|
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
|
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
|
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
|
-
|
140
|
+
emit_ld(dst, src)
|
124
141
|
end
|
125
142
|
|
126
|
-
def
|
127
|
-
|
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
|
131
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
140
|
-
if val
|
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
|
163
|
-
|
164
|
-
return 0 if
|
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
|
-
|
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
|
-
|
172
|
-
|
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
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|