Skip to content

Commit f0e6cd2

Browse files
authored
Setup standard IO (#162)
1 parent f4dc82e commit f0e6cd2

8 files changed

Lines changed: 132 additions & 52 deletions

File tree

src/chomper/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .os import AndroidOs, IosOs
2727
from .os.ios.syscall import SYSCALL_MAP as IOS_SYSCALL_MAP
2828
from .typing import UserData, HookFuncCallable
29-
from .utils import aligned
29+
from .utils import aligned, to_signed
3030

3131

3232
class Chomper:
@@ -439,7 +439,7 @@ def _dispatch_syscall(self):
439439
syscall_name = None
440440

441441
if self.os_type == const.OS_IOS:
442-
syscall_no = self.uc.reg_read(arm64_const.UC_ARM64_REG_X16)
442+
syscall_no = to_signed(self.uc.reg_read(arm64_const.UC_ARM64_REG_W16), 32)
443443
syscall_name = (
444444
f"'{IOS_SYSCALL_MAP[syscall_no]}'"
445445
if syscall_no in IOS_SYSCALL_MAP

src/chomper/os/file.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ def __init__(self, emu: Chomper, rootfs_path: Optional[str]):
5252
# used by fcntl with command F_GETPATH.
5353
self.fd_path_map: Dict[int, str] = {}
5454

55+
if sys.stdin.isatty():
56+
self.stdin_fd = sys.stdin.fileno()
57+
else:
58+
self.stdin_fd = None
59+
60+
self.stdout_fd = sys.stdout.fileno()
61+
self.stderr_fd = sys.stderr.fileno()
62+
5563
def set_working_dir(self, path: str):
5664
"""Set current working directory.
5765
@@ -216,6 +224,9 @@ def check_fd(self, fd: int):
216224
Raises:
217225
BadFileDescriptor: If bad file descriptor.
218226
"""
227+
if fd in (self.stdin_fd, self.stdout_fd, self.stderr_fd):
228+
return
229+
219230
if fd not in self.fd_path_map:
220231
raise FileBadDescriptor(f"Bad file descriptor: {fd}")
221232

src/chomper/os/ios/const.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@
7272
SYS_GETENTROPY = 0x1F4
7373
SYS_ULOCK_WAIT = 0x203
7474

75-
MACH_ABSOLUTE_TIME_TRAP = 0xFFFFFFFD
76-
MACH_TIMEBASE_INFO_TRAP = 0xFFFFFFFFFFFFFFA7
77-
MACH_MSG_TRAP = 0xFFFFFFFFFFFFFFE1
78-
HOST_SELF_TRAP = 0xFFFFFFFFFFFFFFE3
79-
TASK_SELF_TRAP = 0xFFFFFFFFFFFFFFE4
80-
MACH_REPLY_PORT_TRAP = 0xFFFFFFFFFFFFFFE6
81-
82-
KERNELRPC_MACH_VM_MAP_TRAP = 0xFFFFFFFFFFFFFFF1
75+
MACH_ABSOLUTE_TIME_TRAP = -0x3
76+
KERNELRPC_MACH_VM_MAP_TRAP = -0xF
77+
MACH_REPLY_PORT_TRAP = -0x1A
78+
HOST_SELF_TRAP = -0x1D
79+
TASK_SELF_TRAP = -0x1C
80+
MACH_MSG_TRAP = -0x1F
81+
KERNELRPC_MACH_PORT_TYPE_TRAP = -0x4C
82+
MACH_TIMEBASE_INFO_TRAP = -0x59
8383

8484
# CTL Types
8585

@@ -137,11 +137,21 @@
137137

138138
# Command values for fcntl
139139

140+
F_GETFL = 3
140141
F_GETPATH = 50
141142

142-
143143
# Error codes
144144

145145
ENOENT = 2
146146
EBADF = 9
147147
EACCES = 13
148+
149+
# mach/port.h
150+
151+
MACH_PORT_TYPE_NONE = 0
152+
MACH_PORT_TYPE_SEND = 1 << (0 + 16)
153+
MACH_PORT_TYPE_RECEIVE = 1 << (1 + 16)
154+
MACH_PORT_TYPE_SEND_ONCE = 1 << (2 + 16)
155+
MACH_PORT_TYPE_PORT_SET = 1 << (3 + 16)
156+
MACH_PORT_TYPE_DEAD_NAME = 1 << (4 + 16)
157+
MACH_PORT_TYPE_LABELH = 1 << (5 + 16)

src/chomper/os/ios/hooks.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,6 @@ def hook_closedir(uc: Uc, address: int, size: int, user_data: UserData):
108108
return emu.os.file_system.closedir(dirp)
109109

110110

111-
@register_hook("___srefill")
112-
def hook_srefill(uc: Uc, address: int, size: int, user_data: UserData):
113-
return 1
114-
115-
116111
@register_hook("_pthread_self")
117112
def hook_pthread_self(uc: Uc, address: int, size: int, user_data: UserData):
118113
emu = user_data["emu"]

src/chomper/os/ios/os.py

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -294,16 +294,6 @@ def init_objc(self, module: Module):
294294
if not self.emu.find_module("libobjc.A.dylib"):
295295
return
296296

297-
initialized = self.emu.find_symbol("__ZZ10_objc_initE11initialized")
298-
if not self.emu.read_u8(initialized.address):
299-
# As the initialization timing before program execution
300-
self._init_magic_vars()
301-
self._init_program_vars()
302-
self._init_dyld_vars()
303-
self._init_lib_system_kernel()
304-
self._init_lib_system_pthread()
305-
self._init_objc_vars()
306-
307297
text_segment = module.binary.get_segment("__TEXT")
308298

309299
mach_header_ptr = module.base - module.image_base + text_segment.virtual_address
@@ -319,8 +309,6 @@ def init_objc(self, module: Module):
319309
self.emu.call_symbol("_load_images", 0, mach_header_ptr)
320310
except EmulatorCrashed:
321311
self.emu.logger.warning("Initialize Objective-C failed.")
322-
finally:
323-
module.binary = None
324312

325313
def search_module_binary(self, module_name: str) -> str:
326314
"""Search system module binary in rootfs directory.
@@ -365,10 +353,25 @@ def resolve_modules(self, module_names: List[str]):
365353
# Fixup must be executed before initializing Objective-C.
366354
fixup.install(module)
367355

368-
# TODO: `__pthread_init` in `libsystem_pthread.dylib`
356+
self._after_module_loaded(module_name)
369357

370358
self.init_objc(module)
371359

360+
module.binary = None
361+
362+
def _after_module_loaded(self, module_name: str):
363+
"""Perform initialization after module loaded."""
364+
if module_name == "libsystem_kernel.dylib":
365+
self._init_lib_system_kernel()
366+
elif module_name == "libsystem_c.dylib":
367+
self._init_program_vars()
368+
elif module_name == "libdyld.dylib":
369+
self._init_dyld_vars()
370+
elif module_name == "libsystem_pthread.dylib":
371+
self._init_lib_system_pthread()
372+
elif module_name == "libobjc.A.dylib":
373+
self._init_objc_vars()
374+
372375
def _enable_objc(self):
373376
"""Enable Objective-C support."""
374377
self.resolve_modules(OBJC_DEPENDENCIES)
@@ -488,6 +491,37 @@ def fix_method_signature_rom_table(self):
488491
self.emu.write_pointer(offset + 8, str_ptr)
489492
self.emu.write_u64(offset + 16, item[2])
490493

494+
def _fd_open(self, fd: int, mode: str, unbuffered: bool = False) -> int:
495+
mode_p = self.emu.create_string(mode)
496+
497+
try:
498+
fp = self.emu.call_symbol("_fdopen", fd, mode_p)
499+
flags = self.emu.read_u32(fp + 16)
500+
501+
if unbuffered:
502+
flags |= 0x2
503+
504+
self.emu.write_u32(fp + 16, flags)
505+
return fp
506+
finally:
507+
self.emu.free(mode_p)
508+
509+
def _setup_standard_io(self):
510+
"""Setup standard IO: `stdin`, `stdout`, `stderr`."""
511+
stdin_p = self.emu.find_symbol("___stdinp")
512+
stdout_p = self.emu.find_symbol("___stdoutp")
513+
stderr_p = self.emu.find_symbol("___stderrp")
514+
515+
if isinstance(self.file_system.stdin_fd, int):
516+
stdin_fp = self._fd_open(self.file_system.stdin_fd, "r")
517+
self.emu.write_pointer(stdin_p.address, stdin_fp)
518+
519+
stdout_fp = self._fd_open(self.file_system.stdout_fd, "w", unbuffered=True)
520+
self.emu.write_pointer(stdout_p.address, stdout_fp)
521+
522+
stderr_fp = self._fd_open(self.file_system.stderr_fd, "w", unbuffered=True)
523+
self.emu.write_pointer(stderr_p.address, stderr_fp)
524+
491525
def initialize(self):
492526
"""Initialize environment."""
493527
self._setup_hooks()
@@ -497,8 +531,12 @@ def initialize(self):
497531
self._setup_symbolic_links()
498532
self._setup_bundle_dir()
499533

534+
self._init_magic_vars()
535+
500536
if self.emu.enable_objc:
501537
self._enable_objc()
502538

503539
if self.emu.enable_ui_kit:
504540
self._enable_ui_kit()
541+
542+
self._setup_standard_io()

src/chomper/os/ios/syscall.py

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,15 @@ def handle_sys_fcntl(emu: Chomper):
459459
cmd = emu.get_arg(1)
460460
arg = emu.get_arg(2)
461461

462-
if cmd == const.F_GETPATH:
462+
if cmd == const.F_GETFL:
463+
if fd in (emu.os.file_system.stdin_fd,):
464+
return os.O_RDONLY
465+
elif fd in (emu.os.file_system.stdout_fd, emu.os.file_system.stderr_fd):
466+
return os.O_WRONLY
467+
elif cmd == const.F_GETPATH:
463468
path = emu.os.file_system.dir_fds.get(fd)
464-
if not path:
469+
470+
if path is None:
465471
path = emu.os.file_system.fd_path_map.get(fd)
466472

467473
if path:
@@ -764,16 +770,32 @@ def handle_mach_absolute_time_trap(emu: Chomper):
764770
return int(time.time_ns() % (3600 * 10**9))
765771

766772

767-
@register_syscall_handler(const.MACH_TIMEBASE_INFO_TRAP)
768-
def handle_mach_timebase_info_trap(emu: Chomper):
769-
info = emu.get_arg(0)
773+
@register_syscall_handler(const.KERNELRPC_MACH_VM_MAP_TRAP)
774+
def handle_kernelrpc_mach_vm_map_trap(emu: Chomper):
775+
address = emu.get_arg(1)
776+
size = emu.get_arg(2)
770777

771-
emu.write_u32(info, 1)
772-
emu.write_u32(info + 4, 1)
778+
mem = emu.memory_manager.alloc(size)
779+
emu.write_pointer(address, mem)
773780

774781
return 0
775782

776783

784+
@register_syscall_handler(const.MACH_REPLY_PORT_TRAP)
785+
def handle_mach_reply_port_trap(emu: Chomper):
786+
return 0
787+
788+
789+
@register_syscall_handler(const.HOST_SELF_TRAP)
790+
def handle_host_self_trap(emu: Chomper):
791+
return 2563
792+
793+
794+
@register_syscall_handler(const.TASK_SELF_TRAP)
795+
def handle_task_self_trap(emu: Chomper):
796+
return 0
797+
798+
777799
@register_syscall_handler(const.MACH_MSG_TRAP)
778800
def handle_mach_msg_trap(emu: Chomper):
779801
# msg = emu.get_arg(0)
@@ -790,27 +812,24 @@ def handle_mach_msg_trap(emu: Chomper):
790812
return 6
791813

792814

793-
@register_syscall_handler(const.HOST_SELF_TRAP)
794-
def handle_host_self_trap(emu: Chomper):
795-
return 2563
796-
815+
@register_syscall_handler(const.KERNELRPC_MACH_PORT_TYPE_TRAP)
816+
def handle_kernelrpc_mach_port_type_trap(emu: Chomper):
817+
ptype = emu.get_arg(2)
797818

798-
@register_syscall_handler(const.TASK_SELF_TRAP)
799-
def handle_task_self_trap(emu: Chomper):
800-
return 0
819+
value = 0
820+
value |= const.MACH_PORT_TYPE_SEND
821+
value |= const.MACH_PORT_TYPE_RECEIVE
801822

823+
emu.write_u32(ptype, value)
802824

803-
@register_syscall_handler(const.MACH_REPLY_PORT_TRAP)
804-
def handle_mach_reply_port_trap(emu: Chomper):
805825
return 0
806826

807827

808-
@register_syscall_handler(const.KERNELRPC_MACH_VM_MAP_TRAP)
809-
def handle_kernelrpc_mach_vm_map_trap(emu: Chomper):
810-
address = emu.get_arg(1)
811-
size = emu.get_arg(2)
828+
@register_syscall_handler(const.MACH_TIMEBASE_INFO_TRAP)
829+
def handle_mach_timebase_info_trap(emu: Chomper):
830+
info = emu.get_arg(0)
812831

813-
mem = emu.memory_manager.alloc(size)
814-
emu.write_pointer(address, mem)
832+
emu.write_u32(info, 1)
833+
emu.write_u32(info + 4, 1)
815834

816835
return 0

src/chomper/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ def aligned(x: int, n: int) -> int:
1919
return ((x - 1) // n + 1) * n
2020

2121

22+
def to_signed(value: int, nbits: int = 64) -> int:
23+
"""Convert an unsigned integer to signed."""
24+
if value >= (1 << (nbits - 1)):
25+
return value - (1 << nbits)
26+
return value
27+
28+
2229
def struct2bytes(st: Structure) -> bytes:
2330
"""Convert struct to bytes."""
2431
buffer = create_string_buffer(sizeof(st))

tests/test_libc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def test_sscanf(request, emu_name):
224224

225225

226226
@pytest.mark.usefixtures("libc_arm", "libc_arm64")
227-
@pytest.mark.parametrize("emu_name", ["emu_arm", "emu_arm64"])
227+
@pytest.mark.parametrize("emu_name", ["emu_arm", "emu_arm64", "emu_ios"])
228228
def test_printf(request, emu_name):
229229
emu = request.getfixturevalue(emu_name)
230230

0 commit comments

Comments
 (0)