Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pylint integration and more tests #17

Merged
merged 2 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ jobs:
- name: flake8
run: pipenv run flake8
- name: pylint
run: pipenv run pylint src
run: |
pipenv run pylint src test
pipenv run pylint -d duplicate-code,invalid-name test/templates/*.py
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does disabling these do anything? I tested this locally with .pylintrc file deleted. I only get following warnings:

Quote delimiter " is inconsistent with the rest of the file

And does above pylint run file already cover template code right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still needed to disable these warnings for scripts in test/templates because otherwise my version of pylint (Ubuntu 20.04) was complaining about invalid case of variables (he wants them all uppercase) and some code duplication.

Copy link
Owner

@Kazhuu Kazhuu Jan 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which version of pylint you are using? Maybe we should force it to specific version in Pipfile instead because of this. I mean if you get different warnings than me.

When I run pytest -v, the first line prints following. I'm running Manjaro with i3.

platform linux -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /home/kazhuu/.local/share/virtualenvs/asm2cfg-954QkiKO/bin/python

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And also are these errors coming up in CI pipeline?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have

yugr@yugr-VirtualBox:~/src/my/asm2cfg$ pipenv run pylint --version
pylint 2.12.2
astroid 2.9.3
Python 3.8.10 (default, Nov 26 2021, 20:14:08) 
[GCC 9.3.0]

I'm not sure how to go about these differences, maybe require a minimum Pylint version if pipenv supports this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah sorry the previous output doesn't even contain the version output. Here is mine:

~/programming/asm2cfg (tests/5*) » pylint --version                                                                                                                                                                                                                                                                                                      127 ↵ kazhuu@kazhuu
pylint 2.12.2
astroid 2.9.3
Python 3.10.1 (main, Dec 18 2021, 23:53:45) [GCC 11.1.0]

It seems we are using the same version. Wonder where you get the errors. What is the specific line you use to run pylint?

Coverage:
needs: [Lint, Baseline]
runs-on: ubuntu-latest
Expand Down
23 changes: 13 additions & 10 deletions test/templates/common.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
"""
Common code shared by all generators
"""

import subprocess
import sys
import re
import os

_me = os.path.basename(__file__)
verbose = 0
_ME = os.path.basename(__file__)


def set_basename(file):
global _me
_me = file
global _ME # pylint: disable=global-statement
_ME = file


def error(msg):
"""
Print nicely-formatted error message and exit.
"""
sys.stderr.write(f'{_me}: error: {msg}\n')
sys.stderr.write(f'{_ME}: error: {msg}\n')
sys.exit(1)


def _run(cmd, stdin=None):
def _run(cmd, stdin=None, verbose=0):
"""
Run process and abort on error.
"""
if verbose:
print(f"{_me}: running command: {' '.join(cmd)}")
print(f"{_ME}: running command: {' '.join(cmd)}")
with subprocess.Popen(cmd, stdin=stdin, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as process:
out, err = process.communicate()
Expand Down Expand Up @@ -65,15 +68,15 @@ def strip_binary(file):
_run(['strip', '-s', file])


def grep(s, regex):
lines = s.split('\n')
def grep(text, regex):
lines = text.split('\n')
return list(filter(lambda s: re.search(regex, s), lines))


def find_address(file, name):
out = _run(['readelf', '-sW', file])
lines = grep(out, fr'{name}$')
assert len(lines) >= 1, f"failed to locate symbol {name} in\n{out}"
assert len(lines) >= 1, f'failed to locate symbol {name} in\n{out}'
line = lines[0]
# Num: Value Size Type Bind Vis Ndx Name
# 27: 0000000000001030 11 FUNC GLOBAL DEFAULT 9 foo
Expand Down
2 changes: 1 addition & 1 deletion test/templates/gen_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
call_type = 'Non-PIC-call' if direct else 'PIC-call'
strip_type = 'stripped' if strip else 'UNstripped'
plt_type = 'PLT' if plt else 'PLT-less'
print(f"Checking {disasm_type} {pic_type} {plt_type} {call_type} {strip_type}")
print(f'Checking {disasm_type} {pic_type} {plt_type} {call_type} {strip_type}')

# Generate object code

Expand Down
2 changes: 1 addition & 1 deletion test/templates/gen_funtable.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
disasm_type = 'GDB' if gdb else 'objdump'
pic_type = 'position-INdependent' if pic else 'position-dependent'
stripped = 'stripped' if strip else 'UNstripped'
print(f"Checking {disasm_type} {pic_type} {stripped}")
print(f'Checking {disasm_type} {pic_type} {stripped}')

# Generate object code

Expand Down
2 changes: 1 addition & 1 deletion test/templates/gen_jumps.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
pic_type = 'position-INdependent' if pic else 'position-dependent'
opt_type = 'optimized' if opt else 'UNoptimized'
stripped = 'stripped' if strip else 'UNstripped'
print(f"Checking {disasm_type} {pic_type} {opt_type} {stripped}")
print(f'Checking {disasm_type} {pic_type} {opt_type} {stripped}')

# Generate object code

Expand Down
2 changes: 1 addition & 1 deletion test/templates/gen_jumptable.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
disasm_type = 'GDB' if gdb else 'objdump'
pic_type = 'position-INdependent' if pic else 'position-dependent'
stripped = 'stripped' if strip else 'UNstripped'
print(f"Checking {disasm_type} {pic_type} {stripped}")
print(f'Checking {disasm_type} {pic_type} {stripped}')

# Generate object code

Expand Down
63 changes: 62 additions & 1 deletion test/test_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
"""
Unittests of asm2cfg's assembly parser
"""

import unittest

from src.asm2cfg import asm2cfg
Expand All @@ -9,6 +13,10 @@ def _get_the_source_block(blocks):


class ParseLineTestCase(unittest.TestCase):
"""
Tests of parse_line function
"""

@unittest.expectedFailure
def test_linear_sequence(self):
lines = '''\
Expand Down Expand Up @@ -78,7 +86,60 @@ def test_conditional(self):
self.assertIs(dst_block.no_jump_edge, None)
self.assertEqual(len(dst_block.instructions), 2)

@unittest.expectedFailure
def test_jumptables(self):
lines = '''\
Dump of assembler code for function bar:
0x0000000000001070 <+0>: endbr64
0x0000000000001074 <+4>: cmp $0x9,%edi
0x0000000000001077 <+7>: ja 0x1120 <bar+176>
0x000000000000107d <+13>: lea 0xf80(%rip),%rdx # 0x2004
0x0000000000001084 <+20>: mov %edi,%edi
0x0000000000001086 <+22>: sub $0x8,%rsp
0x000000000000108a <+26>: movslq (%rdx,%rdi,4),%rax
0x000000000000108e <+30>: add %rdx,%rax
0x0000000000001091 <+33>: notrack jmpq *%rax
0x0000000000001094 <+36>: nopl 0x0(%rax)
0x0000000000001098 <+40>: xor %eax,%eax
0x000000000000109a <+42>: callq 0x1040 <foo@plt>
0x000000000000109f <+47>: mov $0xc,%eax
0x00000000000010a4 <+52>: add $0x8,%rsp
0x00000000000010a8 <+56>: retq
0x00000000000010a9 <+57>: nopl 0x0(%rax)
0x00000000000010b0 <+64>: lea 0xf49(%rip),%rdi # 0x2000
0x00000000000010b7 <+71>: xor %eax,%eax
0x00000000000010b9 <+73>: callq 0x1030 <baz@plt>
0x00000000000010be <+78>: mov $0xffffffe7,%eax
0x00000000000010c3 <+83>: add $0x8,%rsp
0x00000000000010c7 <+87>: retq
0x00000000000010c8 <+88>: nopl 0x0(%rax,%rax,1)
0x00000000000010d0 <+96>: mov $0xffffffe5,%eax
0x00000000000010d5 <+101>: add $0x8,%rsp
0x00000000000010d9 <+105>: retq
0x00000000000010da <+106>: nopw 0x0(%rax,%rax,1)
0x00000000000010e0 <+112>: mov $0x21,%eax
0x00000000000010e5 <+117>: add $0x8,%rsp
0x00000000000010e9 <+121>: retq
0x00000000000010ea <+122>: nopw 0x0(%rax,%rax,1)
0x00000000000010f0 <+128>: mov $0x23,%eax
0x00000000000010f5 <+133>: add $0x8,%rsp
0x00000000000010f9 <+137>: retq
0x00000000000010fa <+138>: nopw 0x0(%rax,%rax,1)
0x0000000000001100 <+144>: mov $0x63,%eax
0x0000000000001105 <+149>: add $0x8,%rsp
0x0000000000001109 <+153>: retq
0x000000000000110a <+154>: nopw 0x0(%rax,%rax,1)
0x0000000000001110 <+160>: mov $0xa,%eax
0x0000000000001115 <+165>: add $0x8,%rsp
0x0000000000001119 <+169>: retq
0x000000000000111a <+170>: nopw 0x0(%rax,%rax,1)
0x0000000000001120 <+176>: retq
'''.split('\n')
_, blocks = asm2cfg.parse_lines(lines, False)

# TODO: special block for indirect jumps
self.assertEqual(len(blocks), 4)

# TODO:
# - functions (with and w/o calls)
# - jumptables
# - skip calls
16 changes: 16 additions & 0 deletions test/test_regex.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
"""
Unittests of asm2cfg's regexes
"""

import unittest

from src.asm2cfg import asm2cfg


class FunctionHeaderTestCase(unittest.TestCase):
"""
Tests of function header regex
"""

def test_gdb_unstripped(self):
line = 'Dump of assembler code for function test_function:'
strip, fun = asm2cfg.get_stripped_and_function_name(line)
Expand All @@ -28,6 +36,10 @@ def test_objdump(self):


class CallPatternTestCase(unittest.TestCase):
"""
Tests of call instruction regex
"""

def setUp(self):
self.strip_regex = asm2cfg.get_call_pattern(True)
self.unstrip_regex = asm2cfg.get_call_pattern(False)
Expand Down Expand Up @@ -144,6 +156,10 @@ def test_objdump_indirect_call(self):


class JumpPatternTestCase(unittest.TestCase):
"""
Tests of jump instruction regex
"""

def test_gdb_stripped(self):
line = '0x000055555555600f: jmp 0x55555555603d'
pattern = asm2cfg.get_jump_pattern(True, 'does_not_matter')
Expand Down