Skip to content

Commit 79c9723

Browse files
committed
Add experimental support for consts
1 parent 6d0352e commit 79c9723

File tree

11 files changed

+211
-8
lines changed

11 files changed

+211
-8
lines changed

README.md

+11-2
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,20 @@ Library Dependency:
2222
- pywin32 (when handling Windows process)
2323

2424
External Program:
25-
- When using `SSH` function:
25+
- `SSH` requires:
2626
- ssh
2727
- expect
28-
- When using `nasm` function:
28+
- `nasm` requires:
2929
- nasm
30+
- `assemble` requires:
31+
- gcc, objcopy (x86, x86-64)
32+
- arm-linux-gnueabi-gcc, aarch64-linux-gnu-gcc (arm, aarch64)
33+
- `disassemble` requires:
34+
- objdump (x86, x86-64)
35+
- arm-linux-gnueabi-objdump, aarch64-linux-gnu-objdump (arm, aarch64)
36+
- `consts` requires:
37+
- grep
38+
- gcc (x86, x86-64)
3039

3140
## Usage
3241
Basic examples are available at [/examples](https://github.com/ptr-yudai/ptrlib/tree/master/examples/).

ptrlib/arch/arm/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .archname import *
22
from .assembler import *
3+
from .consts import *
34
from .disassembler import *
45
from .syscall import *

ptrlib/arch/arm/consts.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import functools
2+
from typing import Union
3+
4+
try:
5+
cache = functools.cache
6+
except AttributeError:
7+
cache = functools.lru_cache
8+
9+
10+
class ConstsTableArm(object):
11+
@cache
12+
def __getitem__(self, name: str) -> Union[int, str]:
13+
raise NotImplementedError("ARM is not supported yet")
14+
15+
def __getattr__(self, name: str):
16+
return self[name]

ptrlib/arch/arm/syscall.py

+2-2
Large diffs are not rendered by default.

ptrlib/arch/intel/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .archname import *
22
from .assembler import *
3+
from .consts import *
34
from .cpu import *
45
from .disassembler import *
56
from .simd import *

ptrlib/arch/intel/consts.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import functools
2+
from typing import Union
3+
4+
try:
5+
cache = functools.cache
6+
except AttributeError:
7+
cache = functools.lru_cache
8+
9+
10+
class ConstsTableIntel(object):
11+
@cache
12+
def __getitem__(self, name: str) -> Union[int, str]:
13+
from ptrlib.arch.linux import consts
14+
return consts.resolve_constant(
15+
name,
16+
['/usr/include/x86_64-linux-gnu',
17+
'/usr/include/x86_64-linux-musl']
18+
)
19+
20+
def __getattr__(self, name: str):
21+
return self[name]

ptrlib/arch/intel/syscall.py

+2-2
Large diffs are not rendered by default.

ptrlib/arch/linux/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .consts import *
12
from .ospath import *
23
from .sig import *
34
from .syscall import *

ptrlib/arch/linux/consts.py

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import contextlib
2+
import functools
3+
import os
4+
import platform
5+
import re
6+
import subprocess
7+
import tempfile
8+
from typing import List, Optional, Union
9+
from ptrlib.arch.arm import is_arch_arm, ConstsTableArm
10+
from ptrlib.arch.intel import is_arch_intel, ConstsTableIntel
11+
12+
try:
13+
cache = functools.cache
14+
except AttributeError:
15+
cache = functools.lru_cache
16+
17+
18+
_TEMPLATE_C = """
19+
#include <stdio.h>
20+
#include <{0}>
21+
22+
#define print_const(X) (void)_Generic((X), \
23+
char*: printf("S:%s\\n", (const char*)(X)), \
24+
default: printf("V:%lu\\n", (size_t)(X)) \
25+
)
26+
27+
int main() {{
28+
print_const({1});
29+
return 0;
30+
}}
31+
"""
32+
33+
# ConstsTableLinux: Experimental feature
34+
class ConstsTableLinux(object):
35+
def resolve_constant(self,
36+
const: str,
37+
include_path: Optional[List[str]] = None) -> Union[int, str]:
38+
from ptrlib.arch.common import which
39+
40+
if len(const) == 0:
41+
raise KeyError("Empty name '{}'".format(const))
42+
43+
if include_path is not None:
44+
include_path = include_path + ['/usr/include']
45+
else:
46+
include_path = ['/usr/include']
47+
48+
def heuristic_redirect(path: str) -> str:
49+
"""Convert include path"""
50+
with open(path, 'r') as f:
51+
buf = f.read()
52+
found = re.findall(r"Never use <.+> directly; include <(.+)> instead\.", buf)
53+
if found:
54+
return found[0]
55+
else:
56+
return path
57+
58+
def test_constant(path: str, name: str, gcc_path: str) -> Optional[Union[int, str]]:
59+
"""Compile and run C code to get constant value"""
60+
path = heuristic_redirect(path)
61+
fname_c = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())+'.c'
62+
fname_bin = os.path.join(tempfile.gettempdir(), os.urandom(24).hex())+'.bin'
63+
with open(fname_c, 'w') as f:
64+
f.write(_TEMPLATE_C.format(path, name))
65+
66+
with contextlib.suppress(FileNotFoundError):
67+
p = subprocess.run([gcc_path, fname_c, '-o', fname_bin],
68+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
69+
os.unlink(fname_c)
70+
71+
if p.returncode == 0:
72+
p = subprocess.run([fname_bin],
73+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
74+
os.unlink(fname_bin)
75+
76+
if p.returncode == 0:
77+
if p.stdout.startswith(b"S:"):
78+
return p.stdout[2:].decode().strip()
79+
elif p.stdout.startswith(b"V:"):
80+
return int(p.stdout[2:])
81+
else:
82+
raise RuntimeError(f"Unexpected output: {p.stdout.decode()}")
83+
84+
return
85+
86+
# We rely on grep since it's much faster
87+
grep_path = which('grep')
88+
if grep_path is None:
89+
raise FileNotFoundError("'grep' not found")
90+
91+
if is_arch_intel(platform.machine()):
92+
gcc_path = which('gcc')
93+
else:
94+
gcc_path = which('x86_64-linux-gnu-gcc')
95+
if gcc_path is None:
96+
raise FileNotFoundError("Install 'gcc' for this architecture")
97+
98+
for dpath in include_path:
99+
# We can directly build regex since `const` is a valid Python variable name
100+
p = subprocess.run([grep_path, '-E', f'#\\s*define\\s+{const}', '-rl', dpath],
101+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
102+
if p.returncode != 0:
103+
continue
104+
105+
for path in p.stdout.decode().split('\n'):
106+
if not os.path.exists(path):
107+
continue
108+
109+
c = test_constant(path, const, gcc_path)
110+
if c is not None:
111+
return c
112+
113+
raise KeyError("Could not find constant: {}".format(const))
114+
115+
@cache
116+
def __getitem__(self, const_or_arch: str) -> Union[int, str, ConstsTableIntel, ConstsTableArm]:
117+
if is_arch_intel(const_or_arch):
118+
return ConstsTableIntel()
119+
elif is_arch_arm(const_or_arch):
120+
return ConstsTableArm()
121+
elif const_or_arch.isupper():
122+
return self.resolve_constant(const_or_arch)
123+
else:
124+
raise KeyError("Invalid name '{}'".format(arch))
125+
126+
def __getattr__(self, arch: str):
127+
return self[arch]
128+
129+
130+
consts = ConstsTableLinux()

tests/arch/test_consts.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import unittest
2+
from ptrlib import consts
3+
from logging import getLogger, FATAL
4+
5+
6+
class TestConsts(unittest.TestCase):
7+
def setUp(self):
8+
getLogger("ptrlib").setLevel(FATAL)
9+
10+
def test_consts(self):
11+
self.assertEqual(consts['x86']['EFAULT'], 14)
12+
self.assertEqual(consts['i386']['EPERM'], 1)
13+
self.assertEqual(consts['O_RDWR'], 2)
14+
self.assertEqual(consts['ELF_NOTE_SOLARIS'], "SUNW Solaris")
15+
16+
self.assertEqual(consts.i386.READ_IMPLIES_EXEC, 0x400000)
17+
self.assertEqual(consts.x64.AT_RECURSIVE, 0x8000)
18+
self.assertEqual(consts.amd64.ENOENT, 2)
19+
self.assertEqual(consts.MAP_PRIVATE | consts.MAP_ANONYMOUS, 0x22)
20+
21+
with self.assertRaises(KeyError):
22+
_ = consts.PTRLIB
23+
with self.assertRaises(KeyError):
24+
_ = consts.x64.PTRLIB

utility/gen_syscall_table.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
def syscall_table(arch, bits):
66
table = {}
77
r = requests.get(URL.format(arch=arch, bits=bits))
8-
for num, _, name in re.findall("(\d+).+(common|64|i386).+\ssys_([a-z0-9_]+)", r.text):
8+
for num, _, name in re.findall(r"(\d+).+(common|64|i386).+\ssys_([a-z0-9_]+)", r.text):
99
table[name] = int(num)
1010
return table
1111

1212
def syscall_table_arm64():
1313
# WTF
1414
table = {}
1515
r = requests.get(URL)
16-
for name, num in re.findall("#define\s+__NR_([a-z0-9_]+)\s+(\d+)", r.text):
16+
for name, num in re.findall(r"#define\s+__NR_([a-z0-9_]+)\s+(\d+)", r.text):
1717
table[name] = int(num)
1818
return table
1919

0 commit comments

Comments
 (0)