Add ncrewrite, a tool for rewriting nops to masking instructions before indirect...
[nativeclient.git] / ncv / ncval_test.py
blob86dbd0ad989445412a84e5cd12f7ebe532111a97
2 import os
3 import shutil
4 import subprocess
5 import tempfile
6 import unittest
8 import ncval_stubout
11 def write_file(filename, data):
12 fh = open(filename, "w")
13 try:
14 fh.write(data)
15 finally:
16 fh.close()
19 def read_file(filename):
20 fh = open(filename, "r")
21 try:
22 return fh.read()
23 finally:
24 fh.close()
27 class TempDirTestCase(unittest.TestCase):
29 def setUp(self):
30 self._on_teardown = []
32 def make_temp_dir(self):
33 temp_dir = tempfile.mkdtemp(prefix="tmp-%s-" % self.__class__.__name__)
34 def tear_down():
35 shutil.rmtree(temp_dir)
36 self._on_teardown.append(tear_down)
37 return temp_dir
39 def tearDown(self):
40 for func in reversed(self._on_teardown):
41 func()
44 class NcValTest(TempDirTestCase):
46 def assertEquals(self, x, y):
47 if x != y:
48 if type(x) == str and type(y) == str:
49 raise AssertionError('"%s"\n!="%s"' % (x, y))
50 raise AssertionError("%r != %r" % (x, y))
52 def _get_errors(self, obj_file):
53 proc = subprocess.Popen(["ncval", obj_file], stdout=subprocess.PIPE)
54 lines = [line for line in proc.stdout
55 if line.startswith("VALIDATOR")]
56 self.assertEquals(proc.wait(), 0)
57 return lines
59 def test_stub_out(self):
60 asm_file = os.path.join(self.make_temp_dir(), "example.S")
61 obj_file = os.path.join(self.make_temp_dir(), "example")
62 write_file(asm_file, """
63 .global _start
64 _start:
65 jmp 0x12345678
66 jmp *%ecx
67 mov %fs,%dx
68 ret
69 int $0x80
70 """)
71 subprocess.check_call(["nacl-gcc", "-nostartfiles", asm_file,
72 "-o", obj_file])
73 self.assertEquals("".join(self._get_errors(obj_file)), """\
74 VALIDATOR: 00010000 (00001000:?): Jump target out of range
75 VALIDATOR: 00010005 (00001005:2): Unsafe indirect jump
76 VALIDATOR: 00010007 (00001007:3): Bad prefix
77 VALIDATOR: 00010007 (00001007:3): Illegal instruction
78 VALIDATOR: 0001000a (0000100a:1): Illegal instruction
79 VALIDATOR: 0001000b (0000100b:2): Illegal instruction
80 """)
81 ncval_stubout.main([obj_file])
82 # Can't stub out bad jumps yet
83 self.assertEquals("".join(self._get_errors(obj_file)), """\
84 VALIDATOR: 00010000 (00001000:?): Jump target out of range
85 """)
87 def test_multiple_sections(self):
88 # Check that we handle file indexes correctly in the presence
89 # of multiple ELF sections.
90 asm_file = os.path.join(self.make_temp_dir(), "example.S")
91 obj_file = os.path.join(self.make_temp_dir(), "example")
92 write_file(asm_file, """
93 .global _start
94 _start:
95 nop
96 nop
97 .section .fini, "x", @progbits
98 ret
99 """)
100 subprocess.check_call(["nacl-gcc", "-nostartfiles", asm_file,
101 "-o", obj_file])
102 self.assertNotEquals(self._get_errors(obj_file), [])
103 ncval_stubout.main([obj_file])
104 self.assertEquals(self._get_errors(obj_file), [])
107 class JumpRewriterTest(TempDirTestCase):
109 def _assemble(self, asm_source):
110 asm_file = os.path.join(self.make_temp_dir(), "foo.S")
111 obj_file = os.path.join(self.make_temp_dir(), "foo.o")
112 write_file(asm_file, asm_source)
113 subprocess.check_call(["gcc", "-c", asm_file, "-o", obj_file])
114 return obj_file
116 def _disassemble(self, obj_file):
117 proc = subprocess.Popen(["objdump", "-d", obj_file],
118 stdout=subprocess.PIPE)
119 return proc.communicate()[0]
121 def test_rewriting(self):
122 original = """
123 // Instructions to be rewritten
124 nop; nop; nop
125 jmp *%eax
126 nop; nop; nop
127 jmp *%ebx
128 nop; nop; nop
129 jmp *%ecx
130 nop; nop; nop
131 jmp *%edx
133 nop; nop; nop
134 call *%eax
135 nop; nop; nop
136 call *%ebx
137 nop; nop; nop
138 call *%ecx
139 nop; nop; nop
140 call *%edx
142 rewritten = """
143 and $0xffffffe0, %eax
144 jmp *%eax
145 and $0xffffffe0, %ebx
146 jmp *%ebx
147 and $0xffffffe0, %ecx
148 jmp *%ecx
149 and $0xffffffe0, %edx
150 jmp *%edx
152 and $0xffffffe0, %eax
153 call *%eax
154 and $0xffffffe0, %ebx
155 call *%ebx
156 and $0xffffffe0, %ecx
157 call *%ecx
158 and $0xffffffe0, %edx
159 call *%edx
161 leave_alone = """
162 // These should be left alone
163 nop; nop; nop
164 jmp 0x12345678
165 mov $123, %eax
167 obj_file = self._assemble(original + leave_alone)
168 obj_file_expect = self._assemble(rewritten + leave_alone)
169 subprocess.check_call(["ncrewrite", obj_file])
170 if read_file(obj_file) != read_file(obj_file_expect):
171 raise Exception("Unexpected output:\n%s\nExpected:\n%s" %
172 (self._disassemble(obj_file),
173 self._disassemble(obj_file_expect)))
175 def test_rewriting_missing_nops(self):
176 input_data = """
177 // Not enough preceding nops to rewrite
178 nop; nop
179 jmp *%ecx
181 obj_file = self._assemble(input_data * 2)
182 original = read_file(obj_file)
183 proc = subprocess.Popen(["ncrewrite", obj_file], stderr=subprocess.PIPE)
184 stderr = proc.communicate()[1]
185 self.assertEquals(stderr,
186 "00000002: missing nops\n"
187 "00000006: missing nops\n")
188 self.assertEquals(proc.wait(), 0)
189 # Object file should not have been changed.
190 self.assertEquals(original, read_file(obj_file))
193 if __name__ == "__main__":
194 unittest.main()