blob: f4d801a81da136d48599e91098bf670991d8c63b [file] [log] [blame]
import struct
from nose.plugins.skip import Skip, SkipTest
from parcon import ParseException
import ubpf.assembler
import ubpf.disassembler
import testdata
try:
xrange
except NameError:
xrange = range
# Just for assertion messages
def try_disassemble(inst):
data = struct.pack("=Q", inst)
try:
return ubpf.disassembler.disassemble(data).strip()
except ValueError:
return "<error>"
def check_datafile(filename):
"""
Verify that the result of assembling the 'asm' section matches the
'raw' section.
"""
data = testdata.read(filename)
if 'asm' not in data:
raise SkipTest("no asm section in datafile")
if 'raw' not in data:
raise SkipTest("no raw section in datafile")
bin_result = ubpf.assembler.assemble(data['asm'])
assert len(bin_result) % 8 == 0
assert len(bin_result) / 8 == len(data['raw'])
for i in xrange(0, len(bin_result), 8):
j = int(i/8)
inst, = struct.unpack_from("=Q", bin_result[i:i+8])
exp = data['raw'][j]
if exp != inst:
raise AssertionError("Expected instruction %d to be %#x (%s), but was %#x (%s)" %
(j, exp, try_disassemble(exp), inst, try_disassemble(inst)))
def test_datafiles():
# Nose test generator
# Creates a testcase for each datafile
for filename in testdata.list_files():
yield check_datafile, filename
# Label support tests
def test_label_forward_jump():
"""Test forward jump with label"""
source = """
mov %r0, 1
ja done
mov %r0, 2
done:
exit
"""
bytecode = ubpf.assembler.assemble(source)
# Should have 4 instructions (mov, ja, mov, exit)
assert len(bytecode) == 4 * 8
# Verify the ja instruction has correct offset (+1 to skip mov and reach done/exit)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 8)
assert off == 1, "Expected ja offset=1, got %s" % off
def test_label_backward_jump():
"""Test backward jump (loop) with label"""
source = """
mov %r0, 10
loop:
sub %r0, 1
jne %r0, 0, loop
exit
"""
bytecode = ubpf.assembler.assemble(source)
assert len(bytecode) == 4 * 8
# Verify the jne instruction has correct offset (-2 to jump back to loop)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 16)
# Convert to signed
signed_off = off if off < 32768 else off - 65536
assert signed_off == -2, "Expected jne offset=-2, got %s" % signed_off
def test_label_conditional_jump():
"""Test conditional jump with label"""
source = """
jeq %r1, %r2, equal
mov %r0, 1
ja done
equal:
mov %r0, 0
done:
exit
"""
bytecode = ubpf.assembler.assemble(source)
assert len(bytecode) == 5 * 8
# Verify jeq offset (+2 to skip mov and ja)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 0)
assert off == 2, "Expected jeq offset=2, got %s" % off
# Verify ja offset (+1 to skip mov)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 16)
assert off == 1, "Expected ja offset=1, got %s" % off
def test_label_local_call():
"""Test local function call with label"""
source = """
mov %r1, 5
call local double
exit
double:
add %r1, %r1
mov %r0, %r1
exit
"""
bytecode = ubpf.assembler.assemble(source)
assert len(bytecode) == 6 * 8
# Verify call local has src_reg=1 and correct relative offset
_, regs, _, imm = struct.unpack_from("=BBHI", bytecode, 8)
src_reg = (regs >> 4) & 0xf
assert src_reg == 1, "Expected src_reg=1 for call local, got %s" % src_reg
assert imm == 1, "Expected call local offset=1, got %s" % imm
def test_label_on_same_line():
"""Test label and instruction on same line"""
source = """
loop: sub %r0, 1
jne %r0, 0, loop
exit
"""
bytecode = ubpf.assembler.assemble(source)
assert len(bytecode) == 3 * 8
# Verify jne has correct offset (-2)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 8)
signed_off = off if off < 32768 else off - 65536
assert signed_off == -2, "Expected jne offset=-2, got %s" % signed_off
def test_label_undefined_error():
"""Test error for undefined label"""
source = "ja undefined\nexit"
try:
ubpf.assembler.assemble(source)
assert False, "Should have raised ValueError for undefined label"
except ValueError as e:
assert "Undefined label" in str(e)
def test_label_duplicate_error():
"""Test error for duplicate label"""
source = """
foo:
mov %r0, 1
foo:
exit
"""
try:
ubpf.assembler.assemble(source)
assert False, "Should have raised ValueError for duplicate label"
except ValueError as e:
assert "Duplicate label" in str(e)
def test_label_starts_with_digit_error():
"""Test that labels starting with digits are rejected"""
# Test label definition starting with digit
source = """
1bad:
mov %r0, 1
exit
"""
try:
ubpf.assembler.assemble(source)
assert False, "Should have raised ParseException for label starting with digit"
except ParseException as e:
# Parser should reject this with a parse exception
# Verify it's about the label name constraint
assert "expected" in str(e).lower() or "position" in str(e).lower()
except ValueError:
# Should not be a ValueError (that's for undefined/duplicate labels)
assert False, "Should have raised ParseException, not ValueError"
def test_label_with_lddw():
"""Test label offset calculation with LDDW instruction"""
source = """
lddw %r0, 0x123456789
ja done
mov %r1, 1
done:
exit
"""
bytecode = ubpf.assembler.assemble(source)
# lddw takes 2 slots, ja, mov, exit = 5 instructions total
assert len(bytecode) == 5 * 8
# Verify ja offset accounts for lddw taking 2 slots (+1 to skip mov)
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 16)
assert off == 1, "Expected ja offset=1, got %s" % off
def test_numeric_offset_backward_compatibility():
"""Test that numeric offsets still work"""
source = """
mov %r0, 1
ja +2
mov %r0, 2
exit
"""
bytecode = ubpf.assembler.assemble(source)
assert len(bytecode) == 4 * 8
# Verify ja has offset +2
_, _, off, _ = struct.unpack_from("=BBHI", bytecode, 8)
assert off == 2, "Expected ja offset=2, got %s" % off