diff --git a/Makefile b/Makefile index fd024bdfc..ded52c415 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ NB_CORES := $(shell grep --count '^processor' /proc/cpuinfo) PYLINT_DISABLE:= all -PYLINT_ENABLE := F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode +PYLINT_ENABLE := F,E,unreachable,duplicate-key,unnecessary-semicolon,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,dangerous-default-value,trailing-whitespace,unneeded-not,singleton-comparison,unused-import +PYLINT_TEST_ENABLE := $(PYLINT_ENABLE),line-too-long,multiple-statements,consider-using-f-string,global-variable-not-assigned PYLINT_JOBS := $(NB_CORES) PYLINT_SUGGEST_FIX := y -PYLINT_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_ENABLE) --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX) --exit-zero +PYLINT_COMMON_PARAMETERS := --jobs=$(PYLINT_JOBS) --suggestion-mode=$(PYLINT_SUGGEST_FIX) +PYLINT_GEF_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_ENABLE) $(PYLINT_COMMON_PARAMETERS) +PYLINT_TEST_PARAMETERS := --disable=$(PYLINT_DISABLE) --enable=$(PYLINT_TEST_ENABLE) $(PYLINT_COMMON_PARAMETERS) TARGET := $(shell lscpu | head -1 | sed -e 's/Architecture:\s*//g') test: testbins @@ -23,5 +26,5 @@ testbins: tests/binaries/*.c @$(MAKE) -j $(NB_CORES) -C tests/binaries TARGET=$(TARGET) all lint: - python3 -m pylint $(PYLINT_PARAMETERS) gef.py - python3 -m pylint $(PYLINT_PARAMETERS) tests/*.py + python3 -m pylint $(PYLINT_GEF_PARAMETERS) gef.py + python3 -m pylint $(PYLINT_TEST_PARAMETERS) tests/*.py diff --git a/tests/helpers.py b/tests/helpers.py index c0d383a7d..5f177bb12 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,4 +1,4 @@ -from typing import List, Iterable +from typing import Iterable import re import subprocess import os @@ -21,7 +21,8 @@ def ansi_clean(s: str) -> str: return ansi_escape.sub("", s) -def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str: +def gdb_run_cmd(cmd: str, before: Iterable[str]=(), after: Iterable[str]=(), + target: str=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str: """Execute a command inside GDB. `before` and `after` are lists of commands to be executed before (resp. after) the command to test.""" command = [ @@ -44,10 +45,12 @@ def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str output = b"\n".join(lines) result = None - # The following is necessary because ANSI escape sequences might have been added in the middle of multibyte - # characters, e.g. \x1b[H\x1b[2J is added into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80 - # which causes a UnicodeDecodeError when trying to decode \xe2. - # Such broken multibyte characters would need to be removed, otherwise the test will result in an error. + # The following is necessary because ANSI escape sequences might have been + # added in the middle of multibyte characters, e.g. \x1b[H\x1b[2J is added + # into the middle of \xe2\x94\x80 to become \xe2\x1b[H\x1b[2J\x94\x80 which + # causes a UnicodeDecodeError when trying to decode \xe2. Such broken + # multibyte characters would need to be removed, otherwise the test will + # result in an error. while not result: try: result = output.decode("utf-8") @@ -62,35 +65,47 @@ def gdb_run_cmd(cmd: str, before: List[str]=[], after: List[str]=[], target: str return result -def gdb_run_silent_cmd(cmd, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str: +def gdb_run_silent_cmd(cmd, before: Iterable[str]=(), after: Iterable[str]=(), + target: str=PATH_TO_DEFAULT_BINARY, + strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str: """Disable the output and run entirely the `target` binary.""" - before += ["gef config context.clear_screen False", - "gef config context.layout '-code -stack'", - "run"] + before = [*before, "gef config context.clear_screen False", + "gef config context.layout '-code -stack'", + "run"] return gdb_run_cmd(cmd, before, after, target, strip_ansi) -def gdb_run_cmd_last_line(cmd, before: List[str]=[], after: List[str]=[], target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str: +def gdb_run_cmd_last_line(cmd, before: Iterable[str]=(), after: Iterable[str]=(), + target: str=PATH_TO_DEFAULT_BINARY, + strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str: """Execute a command in GDB, and return only the last line of its output.""" return gdb_run_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] -def gdb_start_silent_cmd(cmd, before: List[str]=[], after: List[str]=[], target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT, context=DEFAULT_CONTEXT) -> str: - """Execute a command in GDB by starting an execution context. This command disables the `context` - and sets a tbreak at the most convenient entry point.""" - before += ["gef config context.clear_screen False", - "gef config context.layout '{}'".format(context), - "entry-break"] +def gdb_start_silent_cmd(cmd, before: Iterable[str]=(), after: Iterable[str]=(), + target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT, + context=DEFAULT_CONTEXT) -> str: + """Execute a command in GDB by starting an execution context. This command + disables the `context` and sets a tbreak at the most convenient entry + point.""" + before = [*before, "gef config context.clear_screen False", + f"gef config context.layout '{context}'", + "entry-break"] return gdb_run_cmd(cmd, before, after, target, strip_ansi) -def gdb_start_silent_cmd_last_line(cmd, before: List[str]=[], after: List[str]=[], target=PATH_TO_DEFAULT_BINARY, strip_ansi=STRIP_ANSI_DEFAULT) -> str: +def gdb_start_silent_cmd_last_line(cmd, before: Iterable[str]=(), after: Iterable[str]=(), + target=PATH_TO_DEFAULT_BINARY, + strip_ansi=STRIP_ANSI_DEFAULT) -> str: """Execute `gdb_start_silent_cmd()` and return only the last line of its output.""" return gdb_start_silent_cmd(cmd, before, after, target, strip_ansi).splitlines()[-1] -def gdb_test_python_method(meth: str, before: str="", after: str="", target: str=PATH_TO_DEFAULT_BINARY, strip_ansi: str=STRIP_ANSI_DEFAULT) -> str: - cmd = "pi {}print({});{}".format(before+";" if before else "", meth, after) +def gdb_test_python_method(meth: str, before: str="", after: str="", + target: str=PATH_TO_DEFAULT_BINARY, + strip_ansi: bool=STRIP_ANSI_DEFAULT) -> str: + brk = before + ";" if before else "" + cmd = f"pi {brk}print({meth});{after}" return gdb_start_silent_cmd(cmd, target=target, strip_ansi=strip_ansi) @@ -106,7 +121,7 @@ def inner_f(*args, **kwargs): return wrapper -def exclude_for_architectures(invalid_architectures: Iterable[str] = []): +def exclude_for_architectures(invalid_architectures: Iterable[str]=()): def wrapper(f): def inner_f(*args, **kwargs): if ARCH not in invalid_architectures: @@ -115,4 +130,4 @@ def inner_f(*args, **kwargs): sys.stderr.write(f"SKIPPED for {ARCH} ") sys.stderr.flush() return inner_f - return wrapper \ No newline at end of file + return wrapper diff --git a/tests/runtests.py b/tests/runtests.py index 316e36924..b356b8d52 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -18,10 +18,9 @@ gdb_start_silent_cmd_last_line, gdb_test_python_method, include_for_architectures, - exclude_for_architectures, ARCH, is_64b -) # pylint: disable=import-error +) class GdbAssertionError(AssertionError): @@ -33,7 +32,7 @@ class GefUnitTestGeneric(unittest.TestCase): @staticmethod def assertException(buf): - """Expect an exception to be raised""" + """Assert that GEF raised an Exception.""" if not ("Python Exception <" in buf or "Traceback" in buf or "'gdb.error'" in buf @@ -43,12 +42,12 @@ def assertException(buf): @staticmethod def assertNoException(buf): - """No exception should be raised""" - if not ("Python Exception <" not in buf - and "Traceback" not in buf - and "'gdb.error'" not in buf - and "Exception raised" not in buf - and "failed to execute properly, reason:" not in buf): + """Assert that no Exception was raised from GEF.""" + if ("Python Exception <" in buf + or "Traceback" in buf + or "'gdb.error'" in buf + or "Exception raised" in buf + or "failed to execute properly, reason:" in buf): raise GdbAssertionError("Unexpected GDB Exception raised") @staticmethod @@ -193,7 +192,7 @@ def test_cmd_got(self): def test_cmd_gef_remote(self): def start_gdbserver(exe="/tmp/default.out", port=1234): - return subprocess.Popen(["gdbserver", ":{}".format(port), exe], + return subprocess.Popen(["gdbserver", f":{port}", exe], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def stop_gdbserver(gdbserver): @@ -318,7 +317,7 @@ def test_cmd_heap_analysis(self): self.assertIn("calloc(32)=", res) addr = int(res.split("calloc(32)=")[1].split("\n")[0], 0) self.assertRegex(res, r"realloc\(.+, 48") - self.assertIn("free({:#x}".format(addr), res) + self.assertIn(f"free({addr:#x}", res) return def test_cmd_hexdump(self): @@ -357,7 +356,7 @@ def test_cmd_memory_watch(self): self.assertNotIn("cafebabe", res) res = gdb_start_silent_cmd("memory watch &myglobal", before=["set args 0xcafebabe",], - after=["continue", ], + after=["continue"], target=target, context="memory") self.assertIn("cafebabe", res) @@ -438,13 +437,15 @@ def test_cmd_patch_word(self): return def test_cmd_patch_dword(self): - res = gdb_start_silent_cmd_last_line("patch dword $pc 0xcccccccc", after=["display/8bx $pc",]) + res = gdb_start_silent_cmd_last_line("patch dword $pc 0xcccccccc", + after=["display/8bx $pc",]) self.assertNoException(res) self.assertRegex(res, r"(0xcc\s*)(\1\1\1)0x[^c]{2}") return def test_cmd_patch_qword(self): - res = gdb_start_silent_cmd_last_line("patch qword $pc 0xcccccccccccccccc", after=["display/8bx $pc",]) + res = gdb_start_silent_cmd_last_line("patch qword $pc 0xcccccccccccccccc", + after=["display/8bx $pc",]) self.assertNoException(res) self.assertRegex(res, r"(0xcc\s*)(\1\1\1\1\1\1)0xcc") return @@ -460,7 +461,8 @@ def test_cmd_patch_qword_symbol(self): return def test_cmd_patch_string(self): - res = gdb_start_silent_cmd_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"", after=["grep Gef!Gef!Gef!Gef!",]) + res = gdb_start_silent_cmd_last_line("patch string $sp \"Gef!Gef!Gef!Gef!\"", + after=["grep Gef!Gef!Gef!Gef!",]) self.assertNoException(res) self.assertIn("Gef!Gef!Gef!Gef!", res) return @@ -490,13 +492,15 @@ def test_cmd_pattern_search(self): cmd = f"pattern search {r}" target = "/tmp/pattern.out" - res = gdb_run_cmd(cmd, before=["set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run"], target=target) + res = gdb_run_cmd(cmd, before=["set args aaaabaaacaaadaaaeaaafaaagaaahaaa", "run"], + target=target) self.assertNoException(res) self.assertIn("Found at offset", res) cmd = f"pattern search --period 8 {r}" target = "/tmp/pattern.out" - res = gdb_run_cmd(cmd, before=["set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run"], target=target) + res = gdb_run_cmd(cmd, before=["set args aaaaaaaabaaaaaaacaaaaaaadaaaaaaa", "run"], + target=target) self.assertNoException(res) self.assertIn("Found at offset", res) return @@ -524,15 +528,18 @@ def test_cmd_process_status(self): return def test_cmd_process_search(self): - res = gdb_start_silent_cmd("process-search", target="/tmp/pattern.out", before=["set args w00tw00t", ]) + res = gdb_start_silent_cmd("process-search", target="/tmp/pattern.out", + before=["set args w00tw00t"]) self.assertNoException(res) self.assertIn("/tmp/pattern.out", res) - res = gdb_start_silent_cmd("process-search gdb.*fakefake", target="/tmp/pattern.out", before=["set args w00tw00t", ]) + res = gdb_start_silent_cmd("process-search gdb.*fakefake", + target="/tmp/pattern.out", before=["set args w00tw00t"]) self.assertNoException(res) self.assertIn("gdb", res) - res = gdb_start_silent_cmd("process-search --smart-scan gdb.*fakefake", target="/tmp/pattern.out", before=["set args w00tw00t", ]) + res = gdb_start_silent_cmd("process-search --smart-scan gdb.*fakefake", + target="/tmp/pattern.out", before=["set args w00tw00t"]) self.assertNoException(res) self.assertNotIn("gdb", res) return @@ -605,7 +612,8 @@ def test_cmd_set_permission(self): stack_address = int(stack_line.split()[0], 0) # compare the new permissions - res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}", after=[f"xinfo {stack_address:#x}",], target=target) + res = gdb_start_silent_cmd(f"set-permission {stack_address:#x}", + after=[f"xinfo {stack_address:#x}",], target=target) self.assertNoException(res) line = [l.strip() for l in res.splitlines() if l.startswith("Permissions: ")][0] self.assertEqual(line.split()[1], "rwx") @@ -677,12 +685,12 @@ def test_cmd_theme(self): ] for t in possible_themes: # testing command viewing - res = gdb_run_cmd("theme {}".format(t)) + res = gdb_run_cmd(f"theme {t}") self.assertNoException(res) # testing command setting v = "blue blah 10 -1 0xfff bold" - res = gdb_run_cmd("theme {} {}".format(t, v)) + res = gdb_run_cmd(f"theme {t} {v}") self.assertNoException(res) return @@ -692,7 +700,8 @@ def test_cmd_trace_run(self): self.assertFailIfInactiveSession(res) cmd = "trace-run $pc+1" - res = gdb_start_silent_cmd(cmd, before=["gef config trace-run.tracefile_prefix /tmp/gef-trace-"]) + res = gdb_start_silent_cmd(cmd, + before=["gef config trace-run.tracefile_prefix /tmp/gef-trace-"]) self.assertNoException(res) self.assertIn("Tracing from", res) return @@ -700,13 +709,13 @@ def test_cmd_trace_run(self): @include_for_architectures(["x86_64"]) def test_cmd_unicorn_emulate(self): nb_insn = 4 - cmd = "emu {}".format(nb_insn) + cmd = f"emu {nb_insn}" res = gdb_run_silent_cmd(cmd) self.assertFailIfInactiveSession(res) target = "/tmp/unicorn.out" - before = ["break function1", ] - after = ["si", ] + before = ["break function1"] + after = ["si"] start_marker = "= Starting emulation =" end_marker = "Final registers" res = gdb_run_silent_cmd(cmd, target=target, before=before, after=after) @@ -785,11 +794,14 @@ def test_cmd_aliases(self): add_res = gdb_start_silent_cmd("aliases add alias_function_test example") self.assertNoException(add_res) # test list functionality - list_res = gdb_start_silent_cmd("aliases ls", before=["aliases add alias_function_test example"]) + list_res = gdb_start_silent_cmd("aliases ls", + before=["aliases add alias_function_test example"]) self.assertNoException(list_res) self.assertIn("alias_function_test", list_res) # test rm functionality - rm_res = gdb_start_silent_cmd("aliases ls", before=["aliases add alias_function_test example", "aliases rm alias_function_test"]) + rm_res = gdb_start_silent_cmd("aliases ls", + before=["aliases add alias_function_test example", + "aliases rm alias_function_test"]) self.assertNoException(rm_res) self.assertNotIn("alias_function_test", rm_res) return @@ -948,9 +960,12 @@ def test_registers_show_registers_in_correct_order(self): """Ensure the registers are printed in the correct order (PR #670).""" cmd = "registers" if ARCH == "i686": - registers_in_correct_order = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", "$edi", "$eip", "$eflags", "$cs", ] + registers_in_correct_order = ["$eax", "$ebx", "$ecx", "$edx", "$esp", "$ebp", "$esi", + "$edi", "$eip", "$eflags", "$cs"] elif ARCH == "x86_64": - registers_in_correct_order = ["$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", "$rdi", "$rip", "$r8", "$r9", "$r10", "$r11", "$r12", "$r13", "$r14", "$r15", "$eflags", "$cs", ] + registers_in_correct_order = ["$rax", "$rbx", "$rcx", "$rdx", "$rsp", "$rbp", "$rsi", + "$rdi", "$rip", "$r8", "$r9", "$r10", "$r11", "$r12", + "$r13", "$r14", "$r15", "$eflags", "$cs"] else: raise ValueError("Unknown architecture") lines = gdb_start_silent_cmd(cmd).splitlines()[-len(registers_in_correct_order):] @@ -961,8 +976,9 @@ def test_registers_show_registers_in_correct_order(self): @include_for_architectures(["x86_64",]) def test_context_correct_registers_refresh_with_frames(self): """Ensure registers are correctly refreshed when changing frame (PR #668)""" - lines = gdb_run_silent_cmd("registers", after=["frame 5", "registers"], target="/tmp/nested.out").splitlines() - rips = [ x for x in lines if x.startswith("$rip") ] + lines = gdb_run_silent_cmd("registers", after=["frame 5", "registers"], + target="/tmp/nested.out").splitlines() + rips = [x for x in lines if x.startswith("$rip")] self.assertEqual(len(rips), 2) # we must have only 2 entries self.assertNotEqual(rips[0], rips[1]) # they must be different self.assertIn("