From 0232d80550b390f0c30c4355151d092fafa940fa Mon Sep 17 00:00:00 2001 From: Derek Bruening Date: Fri, 8 Apr 2022 17:00:32 -0400 Subject: [PATCH] i#38 attach: Enable attach on AArch64 Implements missing functionality for ptrace attach on AArch64 and AArch32: generated code sequences were previously x86-only; -skip_syscall handling only supported x86; and AArch64 does not support PTRACE_POKEUSER or PTRACE_PEEKUSER. For AArch32, Thumb vs Arm mode require multiple steps: clearing LSB to point at the path used as data via a call; switching to Arm mode for DR's _start; setting the LSB of the initial app PC. For AArch32, additionally fixes an encoder error where the opcode is queried before copying a needs-no-encoding instruction. This is required for the instruction used to hold data for injection. Tweaks the disassembler to leave a level 0 instr alone, again to better handle the data-only insruction used for injection. Enables the client.attach test on AArch64 and AArch32. For AArch32, it needs -skip_syscall. Long-term we want that on by default everywhere but we want explicit tests that hit it on all platforms first. Issue: #38 --- api/docs/release.dox | 2 +- core/arch/aarchxx/aarchxx.asm | 4 +- core/ir/arm/encode.c | 8 +- core/ir/disassemble_shared.c | 22 +-- core/ir/opnd.h | 5 +- core/unix/injector.c | 264 ++++++++++++++++++++++++++-------- suite/tests/CMakeLists.txt | 8 +- suite/tests/runall.cmake | 20 ++- tools/drdeploy.c | 4 - 9 files changed, 251 insertions(+), 86 deletions(-) diff --git a/api/docs/release.dox b/api/docs/release.dox index 0ff308a3809..320852f7568 100644 --- a/api/docs/release.dox +++ b/api/docs/release.dox @@ -129,6 +129,7 @@ changes: - Nothing yet (placeholder). Further non-compatibility-affecting changes include: + - Added AArchXX support for attaching to a running process. - Added new fields analyze_case_ex and instrument_instr_ex to #drbbdup_options_t. - Added drbbdup support to drwrap via #DRWRAP_INVERT_CONTROL, drwrap_invoke_insert(), and drwrap_invoke_insert_cleanup_only(). @@ -1983,7 +1984,6 @@ We hope to include the following major features in future releases: parent process, injection is very early (before kernel32.dll is loaded), but we plan to provide injection at the very first user-mode instruction in the future. - - Linux externally-triggered attaching. - Persistent and process-shared code caches. - Full control over trace building. diff --git a/core/arch/aarchxx/aarchxx.asm b/core/arch/aarchxx/aarchxx.asm index 9acbe9877ff..b131d0a8702 100644 --- a/core/arch/aarchxx/aarchxx.asm +++ b/core/arch/aarchxx/aarchxx.asm @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2014-2016 Google, Inc. All rights reserved. + * Copyright (c) 2014-2022 Google, Inc. All rights reserved. * Copyright (c) 2016 ARM Limited. All rights reserved. * **********************************************************/ @@ -42,6 +42,8 @@ START_FILE # if !defined(STANDALONE_UNIT_TEST) && !defined(STATIC_LIBRARY) DECLARE_FUNC(_start) GLOBAL_LABEL(_start:) + /* i#38: Attaching in middle of blocking syscall requires padded null bytes. */ + nop mov FP, #0 /* clear frame ptr for stack trace bottom */ /* i#1676, i#1708: relocate dynamorio if it is not loaded to preferred address. * We call this here to ensure it's safe to access globals once in C code diff --git a/core/ir/arm/encode.c b/core/ir/arm/encode.c index 42d651c1ae4..927e41319be 100644 --- a/core/ir/arm/encode.c +++ b/core/ir/arm/encode.c @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2014-2021 Google, Inc. All rights reserved. + * Copyright (c) 2014-2022 Google, Inc. All rights reserved. * **********************************************************/ /* @@ -3033,7 +3033,6 @@ instr_encode_arch(dcontext_t *dcontext, instr_t *instr, byte *copy_pc, byte *fin } decode_info_init_for_instr(&di, instr); - di.opcode = instr_get_opcode(instr); di.check_reachable = check_reachable; di.start_pc = copy_pc; di.final_pc = final_pc; @@ -3061,6 +3060,11 @@ instr_encode_arch(dcontext_t *dcontext, instr_t *instr, byte *copy_pc, byte *fin } CLIENT_ASSERT(instr_operands_valid(instr), "instr_encode error: operands invalid"); + /* We delay this until after handling raw instrs to avoid trying to get the opcode + * of a data-only instr. + */ + di.opcode = instr_get_opcode(instr); + info = instr_get_instr_info(instr); if (info == NULL) { if (has_instr_opnds != NULL) diff --git a/core/ir/disassemble_shared.c b/core/ir/disassemble_shared.c index 4f0a59c856b..518e42a9632 100644 --- a/core/ir/disassemble_shared.c +++ b/core/ir/disassemble_shared.c @@ -1522,10 +1522,10 @@ instrlist_disassemble(void *drcontext, app_pc tag, instrlist_t *ilist, file_t ou level = 4; /* encode instr and then output as BINARY */ nxt_pc = instr_encode_ignore_reachability(dcontext, instr, bytes); - ASSERT(nxt_pc != NULL); + CLIENT_ASSERT(nxt_pc != NULL, "failed to encode instr"); len = (int)(nxt_pc - bytes); addr = bytes; - CLIENT_ASSERT(len < 64, "instrlist_disassemble: too-long instr"); + CLIENT_ASSERT(len < sizeof(bytes), "instrlist_disassemble: too-long instr"); } else { addr = instr_get_raw_bits(instr); len = instr_length(dcontext, instr); @@ -1565,12 +1565,18 @@ instrlist_disassemble(void *drcontext, app_pc tag, instrlist_t *ilist, file_t ou while (len) { print_file(outfile, " +%-4d %c%d " IF_X64_ELSE("%20s", "%12s"), offs, instr_is_app(instr) ? 'L' : 'm', level, " "); - next_addr = internal_disassemble_to_file( - dcontext, addr, addr, outfile, false, true, - IF_X64_ELSE(" ", - " ")); - if (next_addr == NULL) - break; + /* Leave level 0 alone as it may not be code. */ + if (level == 0) { + print_file(outfile, " <...%d bytes...>\n", instr->length); + next_addr = addr + instr->length; + } else { + next_addr = internal_disassemble_to_file( + dcontext, addr, addr, outfile, false, true, + IF_X64_ELSE(" ", + " ")); + if (next_addr == NULL) + break; + } sz = (int)(next_addr - addr); CLIENT_ASSERT(sz <= len, "instrlist_disassemble: invalid length"); len -= sz; diff --git a/core/ir/opnd.h b/core/ir/opnd.h index 26df160319c..bbcbdc46955 100644 --- a/core/ir/opnd.h +++ b/core/ir/opnd.h @@ -1,5 +1,5 @@ /* ********************************************************** - * Copyright (c) 2011-2021 Google, Inc. All rights reserved. + * Copyright (c) 2011-2022 Google, Inc. All rights reserved. * Copyright (c) 2000-2010 VMware, Inc. All rights reserved. * **********************************************************/ @@ -222,6 +222,7 @@ opnd_create_sized_tls_slot(int offs, opnd_size_t size); /* This should be kept in sync w/ the defines in x86/x86.asm */ enum { #ifdef X86 + DR_SYSNUM_REG = DR_REG_EAX, # ifdef X64 # ifdef UNIX /* SysV ABI calling convention */ @@ -266,12 +267,14 @@ enum { REGPARM_2 = DR_REG_R2, REGPARM_3 = DR_REG_R3, # ifdef X64 + DR_SYSNUM_REG = DR_REG_R8, REGPARM_4 = DR_REG_R4, REGPARM_5 = DR_REG_R5, REGPARM_6 = DR_REG_R6, REGPARM_7 = DR_REG_R7, NUM_REGPARM = 8, # else + DR_SYSNUM_REG = DR_REG_R7, NUM_REGPARM = 4, # endif /* 64/32 */ REDZONE_SIZE = 0, diff --git a/core/unix/injector.c b/core/unix/injector.c index c87649dad42..3eb7c2c8f8e 100644 --- a/core/unix/injector.c +++ b/core/unix/injector.c @@ -903,9 +903,33 @@ enum { REG_PC_OFFSET = offsetof(struct USER_REGS_TYPE, REG_PC_FIELD) }; # define APP instrlist_append # define PRE instrlist_prepend +/* XXX: Ideally we'd use syscall_instr_length() in arch.c but that requires some + * movement or refactoring to get it into a common file/library. + */ +static inline size_t +system_call_length(dr_isa_mode_t mode) +{ +# ifdef X86 + ASSERT(INT_LENGTH == SYSCALL_LENGTH); + ASSERT(SYSENTER_LENGTH == SYSCALL_LENGTH); + return SYSCALL_LENGTH; +# elif defined(AARCH64) + return SVC_LENGTH; +# elif defined(ARM) + return mode == DR_ISA_ARM_THUMB ? SVC_THUMB_LENGTH : SVC_ARM_LENGTH; +# else +# error Unsupported arch. +# endif +} + # ifdef X86 /* X86s are little endian */ enum { SYSCALL_AS_SHORT = 0x050f, SYSENTER_AS_SHORT = 0x340f, INT80_AS_SHORT = 0x80cd }; +# elif defined(AARCH64) +enum { SVC_RAW = 0xd4000001 }; +# elif defined(ARM) +/* XXX: The arm one *could* have a predicate so the 1st nibble could be <0xe. */ +enum { SVC_ARM_RAW = 0xef000000, SVC_THUMB_RAW = 0xdf00 }; # endif enum { ERESTARTSYS = 512, ERESTARTNOINTR = 513, ERESTARTNOHAND = 514 }; @@ -928,14 +952,17 @@ typedef struct _enum_name_pair_t { static const enum_name_pair_t pt_req_map[] = { { PTRACE_TRACEME, "PTRACE_TRACEME" }, { PTRACE_PEEKTEXT, "PTRACE_PEEKTEXT" }, { PTRACE_PEEKDATA, "PTRACE_PEEKDATA" }, - { PTRACE_PEEKUSER, "PTRACE_PEEKUSER" }, { PTRACE_POKETEXT, "PTRACE_POKETEXT" }, { PTRACE_POKEDATA, "PTRACE_POKEDATA" }, - { PTRACE_POKEUSER, "PTRACE_POKEUSER" }, { PTRACE_CONT, "PTRACE_CONT" }, { PTRACE_KILL, "PTRACE_KILL" }, { PTRACE_SINGLESTEP, "PTRACE_SINGLESTEP" }, -# ifndef DR_HOST_AARCH64 +# ifdef DR_HOST_AARCH64 + { PTRACE_GETREGSET, "PTRACE_GETREGSET" }, + { PTRACE_SETREGSET, "PTRACE_SETREGSET" }, +# else + { PTRACE_PEEKUSER, "PTRACE_PEEKUSER" }, + { PTRACE_POKEUSER, "PTRACE_POKEUSER" }, { PTRACE_GETREGS, "PTRACE_GETREGS" }, { PTRACE_SETREGS, "PTRACE_SETREGS" }, { PTRACE_GETFPREGS, "PTRACE_GETFPREGS" }, @@ -1050,53 +1077,75 @@ ptrace_write_memory(pid_t pid, void *dst, void *src, size_t len) /* Push a pointer to a string to the stack. We create a fake instruction with * raw bytes equal to the string we want to put in the injectee. The call will - * pass these invalid instruction bytes, and the return address on the stack - * will point to the string. + * skip over these invalid instruction bytes and set the return address to point + * to the string. */ static void gen_push_string(void *dc, instrlist_t *ilist, const char *msg) { -# ifdef X86 instr_t *after_msg = INSTR_CREATE_label(dc); - instr_t *msg_instr = instr_build_bits(dc, OP_UNDECODED, strlen(msg) + 1); - APP(ilist, INSTR_CREATE_call(dc, opnd_create_instr(after_msg))); + size_t msg_space = strlen(msg) + 1; +# ifdef AARCHXX + msg_space = ALIGN_FORWARD(msg_space, 4); +# endif + instr_t *msg_instr = instr_build_bits(dc, OP_UNDECODED, msg_space); + APP(ilist, XINST_CREATE_call(dc, opnd_create_instr(after_msg))); instr_set_raw_bytes(msg_instr, (byte *)msg, strlen(msg) + 1); instr_set_raw_bits_valid(msg_instr, true); APP(ilist, msg_instr); APP(ilist, after_msg); -# else - /* FIXME i#1551: NYI on ARM */ - ASSERT_NOT_IMPLEMENTED(false); -# endif /* X86 */ +# ifdef AARCH64 + /* Maintain 16-byte alignment by pushing a 2nd reg. */ + APP(ilist, + /* XXX i#2440: There should be a convenience creation macro for this. */ + instr_create_2dst_4src(dc, OP_stp, + opnd_create_base_disp(DR_REG_XSP, DR_REG_NULL, 0, + -2 * (int)sizeof(void *), OPSZ_16), + opnd_create_reg(DR_REG_XSP), opnd_create_reg(DR_REG_LR), + opnd_create_reg(DR_REG_R0), opnd_create_reg(DR_REG_XSP), + OPND_CREATE_INT8(-2 * (int)sizeof(void *)))); +# elif defined(ARM) + /* Handle Thumb mode setting the LSB and skipping the first char ('/'). */ + APP(ilist, + INSTR_CREATE_bic(dc, opnd_create_reg(DR_REG_LR), opnd_create_reg(DR_REG_LR), + OPND_CREATE_INT8(1))); + APP(ilist, INSTR_CREATE_push(dc, opnd_create_reg(DR_REG_LR))); +# endif } static void gen_syscall(void *dc, instrlist_t *ilist, int sysnum, uint num_opnds, opnd_t *args) { -# ifdef X86 uint i; ASSERT(num_opnds <= MAX_SYSCALL_ARGS); APP(ilist, - INSTR_CREATE_mov_imm(dc, opnd_create_reg(DR_REG_XAX), - OPND_CREATE_INTPTR(sysnum))); + XINST_CREATE_load_int(dc, opnd_create_reg(DR_SYSNUM_REG), + OPND_CREATE_INT32(sysnum))); for (i = 0; i < num_opnds; i++) { - if (opnd_is_immed_int(args[i]) || opnd_is_instr(args[i])) { - APP(ilist, - INSTR_CREATE_mov_imm(dc, opnd_create_reg(syscall_regparms[i]), args[i])); + if (opnd_is_immed_int(args[i])) { + instrlist_insert_mov_immed_ptrsz(dc, opnd_get_immed_int(args[i]), + opnd_create_reg(syscall_regparms[i]), ilist, + instrlist_last(ilist), NULL, NULL); + } else if (opnd_is_instr(args[i])) { + instrlist_insert_mov_instr_addr(dc, opnd_get_instr(args[i]), NULL, + opnd_create_reg(syscall_regparms[i]), ilist, + instrlist_last(ilist), NULL, NULL); } else if (opnd_is_base_disp(args[i])) { APP(ilist, - INSTR_CREATE_mov_ld(dc, opnd_create_reg(syscall_regparms[i]), args[i])); + XINST_CREATE_load(dc, opnd_create_reg(syscall_regparms[i]), args[i])); + } else { + ASSERT_NOT_IMPLEMENTED(false); } } /* XXX: Reuse create_syscall_instr() in emit_utils.c. */ +# ifdef X86 # ifdef X64 APP(ilist, INSTR_CREATE_syscall(dc)); # else APP(ilist, INSTR_CREATE_int(dc, OPND_CREATE_INT8((sbyte)0x80))); # endif # else - /* FIXME i#1551: NYI on ARM */ - ASSERT_NOT_IMPLEMENTED(false); + APP(ilist, INSTR_CREATE_svc(dc, opnd_create_immed_int((sbyte)0x0, OPSZ_1))); # endif /* X86 */ } @@ -1106,7 +1155,7 @@ gen_print(void *dc, instrlist_t *ilist, const char *msg) { opnd_t args[MAX_SYSCALL_ARGS]; args[0] = OPND_CREATE_INTPTR(2); - args[1] = OPND_CREATE_MEMPTR(DR_REG_XSP, 0); /* msg is on TOS. */ + args[1] = OPND_CREATE_MEMPTR(DR_REG_XSP, 0); /* msg is on TOS. */ args[2] = OPND_CREATE_INTPTR(strlen(msg)); gen_push_string(dc, ilist, msg); gen_syscall(dc, ilist, SYSNUM_NO_CANCEL(SYS_write), 3, args); @@ -1118,7 +1167,14 @@ unexpected_trace_event(process_id_t pid, int sig_expected, int sig_actual) { if (verbose) { app_pc err_pc; +# ifdef AARCH64 + /* PEEKUSER is not available. */ + struct USER_REGS_TYPE regs; + our_ptrace_getregs(pid, ®s); + err_pc = (app_pc)regs.REG_PC_FIELD; +# else our_ptrace(PTRACE_PEEKUSER, pid, (void *)REG_PC_OFFSET, &err_pc); +# endif fprintf(stderr, "Unexpected trace event. Expected %s, got signal %d " "at pc: %p\n", @@ -1169,6 +1225,22 @@ injectee_run_get_retval(dr_inject_info_t *info, void *dc, instrlist_t *ilist) app_pc pc; long r; ptr_int_t failure = -EUNATCH; /* Unlikely to be used by most syscalls. */ + dr_isa_mode_t app_mode; + + /* Get register state before executing the shellcode. */ + r = our_ptrace_getregs(info->pid, ®s); + if (r < 0) + return r; + +# ifdef X86 + app_mode = IF_X64_ELSE(DR_ISA_AMD64, DR_ISA_IA32); +# elif defined(AARCH64) + app_mode = DR_ISA_ARM_A64; +# elif defined(ARM) + app_mode = TEST(EFLAGS_T, regs.uregs[16]) ? DR_ISA_ARM_THUMB : DR_ISA_ARM_A32; +# else +# error Unsupported arch. +# endif /* For cases where we are not actally getting blocked by a syscall * and wait_syscall is not specified @@ -1177,18 +1249,16 @@ injectee_run_get_retval(dr_inject_info_t *info, void *dc, instrlist_t *ilist) */ uint nop_times = 0; # ifdef X86 - nop_times = SYSCALL_LENGTH; + nop_times = system_call_length(app_mode); +# else + /* The syscall will match the nop length regardless of the mode. */ + nop_times = 1; # endif int i; for (i = 0; i < nop_times; i++) { PRE(ilist, XINST_CREATE_nop(dc)); } - /* Get register state before executing the shellcode. */ - r = our_ptrace_getregs(info->pid, ®s); - if (r < 0) - return r; - /* Use the current PC's page, since it's executable. Our shell code is * always less than one page, so we won't overflow. */ @@ -1207,6 +1277,7 @@ injectee_run_get_retval(dr_inject_info_t *info, void *dc, instrlist_t *ilist) /* Encode ilist into shellcode. */ end_pc = instrlist_encode_to_copy(dc, ilist, shellcode, pc, &shellcode[MAX_SHELL_CODE], true /*jmp*/); + ASSERT(end_pc != NULL); code_size = end_pc - &shellcode[0]; code_size = ALIGN_FORWARD(code_size, sizeof(reg_t)); ASSERT(code_size <= MAX_SHELL_CODE); @@ -1222,24 +1293,40 @@ injectee_run_get_retval(dr_inject_info_t *info, void *dc, instrlist_t *ilist) * execution, tracee PC will be set back to syscall instruction * PC = PC - sizeof(syscall). We have to add offsets to compensate. */ - if (!info->wait_syscall) { - uint offset = 0; -# ifdef X86 - offset = SYSCALL_LENGTH; + uint offset = 0; + if (!info->wait_syscall) + offset = system_call_length(app_mode); +# ifdef AARCH64 + /* POKEUSER is not available. */ + ptr_int_t saved_pc = regs.REG_PC_FIELD; + regs.REG_PC_FIELD = (ptr_int_t)(pc + offset); + r = our_ptrace_setregs(info->pid, ®s); + if (r < 0) + return r; + regs.REG_PC_FIELD = saved_pc; +# else + r = our_ptrace(PTRACE_POKEUSER, info->pid, (void *)REG_PC_OFFSET, pc + offset); + if (r < 0) + return r; # endif - our_ptrace(PTRACE_POKEUSER, info->pid, (void *)REG_PC_OFFSET, pc + offset); - } else { - our_ptrace(PTRACE_POKEUSER, info->pid, (void *)REG_PC_OFFSET, pc); - } if (!continue_until_break(info->pid)) return failure; /* Get return value. */ ret = failure; +# ifdef AARCH64 + /* PEEKUSER is not available. */ + struct USER_REGS_TYPE modified_regs; + r = our_ptrace_getregs(info->pid, &modified_regs); + if (r < 0) + return r; + ret = modified_regs.REG_RETVAL_FIELD; +# else r = our_ptrace(PTRACE_PEEKUSER, info->pid, (void *)offsetof(struct USER_REGS_TYPE, REG_RETVAL_FIELD), &ret); if (r < 0) return r; +# endif /* Put back original code and registers. */ if (!ptrace_write_memory(info->pid, pc, orig_code, code_size)) @@ -1259,6 +1346,7 @@ injectee_open(dr_inject_info_t *info, const char *path, int flags, mode_t mode) instrlist_t *ilist = instrlist_create(dc); opnd_t args[MAX_SYSCALL_ARGS]; int num_args = 0; + gen_push_string(dc, ilist, path); # ifndef SYS_open args[num_args++] = OPND_CREATE_INTPTR(AT_FDCWD); @@ -1475,7 +1563,39 @@ user_regs_to_mc(priv_mcontext_t *mc, struct USER_REGS_TYPE *regs) mc->r15 = regs->uregs[15]; mc->cpsr = regs->uregs[16]; # elif defined(AARCH64) - ASSERT_NOT_IMPLEMENTED(false); /* FIXME i#1569 */ + mc->r0 = regs->regs[0]; + mc->r1 = regs->regs[1]; + mc->r2 = regs->regs[2]; + mc->r3 = regs->regs[3]; + mc->r4 = regs->regs[4]; + mc->r5 = regs->regs[5]; + mc->r6 = regs->regs[6]; + mc->r7 = regs->regs[7]; + mc->r8 = regs->regs[8]; + mc->r9 = regs->regs[9]; + mc->r10 = regs->regs[10]; + mc->r11 = regs->regs[11]; + mc->r12 = regs->regs[12]; + mc->r13 = regs->regs[13]; + mc->r14 = regs->regs[14]; + mc->r15 = regs->regs[15]; + mc->r16 = regs->regs[16]; + mc->r17 = regs->regs[17]; + mc->r18 = regs->regs[18]; + mc->r19 = regs->regs[19]; + mc->r20 = regs->regs[20]; + mc->r21 = regs->regs[21]; + mc->r22 = regs->regs[22]; + mc->r23 = regs->regs[23]; + mc->r24 = regs->regs[24]; + mc->r25 = regs->regs[25]; + mc->r26 = regs->regs[26]; + mc->r27 = regs->regs[27]; + mc->r28 = regs->regs[28]; + mc->r29 = regs->regs[29]; + mc->r30 = regs->regs[30]; + mc->sp = regs->sp; + mc->pc = (app_pc)regs->pc; # endif /* X86/ARM */ } @@ -1542,32 +1662,33 @@ ptrace_singlestep(process_id_t pid) * For X86, we can't be sure if previous bytes are actually a syscall due to * variations in instruction size. Do additional checks if that is the case. */ -# ifdef X86 -/* Ptrace attach is only for X86 for now. - * ifdef above to statisfies compiler complains. - * These ifdef should be removed after we support new architecture. - */ static bool -is_prev_bytes_syscall(process_id_t pid, app_pc src_pc) +is_prev_bytes_syscall(process_id_t pid, app_pc src_pc, dr_isa_mode_t app_mode) { -# ifdef X86 - /* for X86 is concerned, SYSCALL_LENGTH == INT_LENGTH == SYSENTER_LENGTH */ - app_pc syscall_pc = src_pc - SYSCALL_LENGTH; + app_pc syscall_pc = src_pc - system_call_length(app_mode); /* ptrace_read_memory reads by multiple of sizeof(ptr_int_t) */ byte instr_bytes[sizeof(ptr_int_t)]; ptrace_read_memory(pid, instr_bytes, syscall_pc, sizeof(ptr_int_t)); -# ifdef X64 +# ifdef X86 +# ifdef X64 if (*(unsigned short *)instr_bytes == SYSCALL_AS_SHORT) return true; -# else +# else if (*(unsigned short *)instr_bytes == SYSENTER_AS_SHORT || *(unsigned short *)instr_bytes == INT80_AS_SHORT) return true; -# endif -# endif /* X86 */ +# endif +# elif defined(AARCH64) + if (*(unsigned int *)instr_bytes == SVC_RAW) + return true; +# elif defined(ARM) + if (app_mode == DR_ISA_ARM_THUMB && *(unsigned short *)instr_bytes == SVC_THUMB_RAW) + return true; + if (app_mode == DR_ISA_ARM_A32 && *(unsigned int *)instr_bytes == SVC_ARM_RAW) + return true; +# endif return false; } -# endif /* X86 */ /* i#38: Quick explaination for PC offsetting and NOP sleds: * If ptrace happens in middle of blocking syscalls, tracer will get PC address at @@ -1630,9 +1751,7 @@ inject_ptrace(dr_inject_info_t *info, const char *library_path) dr_fd = injectee_open(info, library_path, O_RDONLY, 0); if (dr_fd < 0) { if (verbose) { - fprintf(stderr, - "Unable to open libdynamorio.so in injectee (%d): " - "%s\n", + fprintf(stderr, "Unable to open %s in injectee (%d): %s\n ", library_path, -dr_fd, strerror(-dr_fd)); } return false; @@ -1661,36 +1780,46 @@ inject_ptrace(dr_inject_info_t *info, const char *library_path) */ injected_dr_start = (app_pc)loader.ehdr->e_entry + loader.load_delta; + our_ptrace_getregs(info->pid, ®s); + dr_isa_mode_t app_mode; +# ifdef X86 + app_mode = IF_X64_ELSE(DR_ISA_AMD64, DR_ISA_IA32); +# elif defined(AARCH64) + app_mode = DR_ISA_ARM_A64; +# elif defined(ARM) + app_mode = TEST(EFLAGS_T, regs.uregs[16]) ? DR_ISA_ARM_THUMB : DR_ISA_ARM_A32; +# else +# error Unsupported arch. +# endif + /* While under Ptrace during blocking syscall, upon continuing * execution, tracee PC will be set back to syscall instruction * PC = PC - sizeof(syscall). We have to add offsets to compensate. */ - if (!info->wait_syscall) { - uint offset = 0; -# ifdef X86 - offset = SYSCALL_LENGTH; +# ifdef ARM + dr_isa_mode_t dr_asm_mode = DR_ISA_ARM_A32; +# else + dr_isa_mode_t dr_asm_mode = app_mode; # endif + if (!info->wait_syscall) { + uint offset = system_call_length(dr_asm_mode); injected_dr_start += offset; } elf_loader_destroy(&loader); - our_ptrace_getregs(info->pid, ®s); - /* Hijacking errno value * After attaching with ptrace during blocking syscall, * Errno value is leaked from kernel handling * Mask that value into EINTR */ if (!info->wait_syscall) { -# ifdef X86 - if (is_prev_bytes_syscall(info->pid, (app_pc)regs.REG_PC_FIELD)) { + if (is_prev_bytes_syscall(info->pid, (app_pc)regs.REG_PC_FIELD, app_mode)) { /* prev bytes might can match by accident, so check return value */ if (regs.REG_RETVAL_FIELD == -ERESTARTSYS || regs.REG_RETVAL_FIELD == -ERESTARTNOINTR || regs.REG_RETVAL_FIELD == -ERESTARTNOHAND) regs.REG_RETVAL_FIELD = -EINTR; } -# endif } /* Create an injection context and "push" it onto the stack of the injectee. @@ -1700,6 +1829,10 @@ inject_ptrace(dr_inject_info_t *info, const char *library_path) memset(&args, 0, sizeof(args)); user_regs_to_mc(&args.mc, ®s); args.argc = ARGC_PTRACE_SENTINEL; +# ifdef ARM + if (app_mode == DR_ISA_ARM_THUMB) + args.mc.pc = PC_AS_JMP_TGT(app_mode, args.mc.pc); +# endif /* We need to send the home directory over. It's hard to find the * environment in the injectee, and even if we could HOME might be @@ -1723,6 +1856,11 @@ inject_ptrace(dr_inject_info_t *info, const char *library_path) # endif regs.REG_PC_FIELD = (ptr_int_t)injected_dr_start; +# ifdef ARM + /* DR's assembly is ARM. */ + ASSERT(dr_asm_mode == DR_ISA_ARM_A32); + regs.uregs[16] &= ~EFLAGS_T; +# endif our_ptrace_setregs(info->pid, ®s); if (op_exec_gdb) { diff --git a/suite/tests/CMakeLists.txt b/suite/tests/CMakeLists.txt index f0cd8bed288..ef16062c639 100644 --- a/suite/tests/CMakeLists.txt +++ b/suite/tests/CMakeLists.txt @@ -2386,10 +2386,10 @@ if (ANNOTATIONS AND NOT ARM) endif (ANNOTATIONS AND NOT ARM) if (NOT ANDROID) # TODO i#38: Port test to Android. - if (X86) # TODO i#38,i#1550: Port injector.c attach gencode to AArchXX. - # TODO i#38: Add targeted Linux tests of attaching during a blocking syscall. - tobuild_ci(client.attach_test client-interface/attach_test.runall "" "" "") - endif () + # TODO i#38: Add targeted Linux tests of attaching during a blocking syscall. + # That seems to happen with the infloop test on ARM already (we have to add + # -skip_syscall in runall.cmake). + tobuild_ci(client.attach_test client-interface/attach_test.runall "" "" "") endif () if (UNIX) diff --git a/suite/tests/runall.cmake b/suite/tests/runall.cmake index 815ae399913..10210d09316 100644 --- a/suite/tests/runall.cmake +++ b/suite/tests/runall.cmake @@ -1,5 +1,5 @@ # ********************************************************** -# Copyright (c) 2018-2020 Google, Inc. All rights reserved. +# Copyright (c) 2018-2022 Google, Inc. All rights reserved. # Copyright (c) 2009-2010 VMware, Inc. All rights reserved. # ********************************************************** @@ -197,7 +197,23 @@ if ("${nudge}" MATCHES "") endif () elseif ("${nudge}" MATCHES "") set(nudge_cmd run_in_bg) - string(REGEX REPLACE "" "${toolbindir}/drrun@-attach@${pid}@-takeover_sleep@-takeovers@1000" nudge "${nudge}") + + set(extra_ops "") + if (UNIX) + # We're not yet explicitly testing -skip_syscall b/c it's considered experimental, + # but we seem to need it on ARM where infloop is in a blocking syscall. + # Not sure why it's different vs other arches. + # XXX i#38: Add additional explicit tests of blocking syscalls, and then turn + # -skip_syscall on by default all the time. + execute_process(COMMAND uname -m OUTPUT_VARIABLE HOST_ARCH) + if ("${HOST_ARCH}" MATCHES "^arm") + set(extra_ops "@-skip_syscall") + endif () + endif () + + string(REGEX REPLACE "" + "${toolbindir}/drrun@-attach@${pid}${extra_ops}@-takeover_sleep@-takeovers@1000" + nudge "${nudge}") string(REGEX REPLACE "@" ";" nudge "${nudge}") execute_process(COMMAND "${toolbindir}/${nudge_cmd}" ${nudge} RESULT_VARIABLE nudge_result diff --git a/tools/drdeploy.c b/tools/drdeploy.c index 1d68bfbcb29..c9c5c5521ff 100644 --- a/tools/drdeploy.c +++ b/tools/drdeploy.c @@ -304,13 +304,11 @@ const char *options_list_str = " as well-supported as launching a new process.\n" " When attaching to a process in the middle of a blocking\n" " system call, DynamoRIO will wait until it returns.\n" -# ifdef X86 " Use -skip_syscall to force interruption.\n" " -skip_syscall (Experimental)\n" " Only works with -attach.\n" " Attaching to a process will force blocking system calls\n" " to fail with EINTR.\n" -# endif # endif # ifdef WINDOWS " -attach Attach to the process with the given pid.\n" @@ -1382,12 +1380,10 @@ _tmain(int argc, TCHAR *targv[]) continue; } # ifdef UNIX -# ifdef X86 else if (strcmp(argv[i], "-skip_syscall") == 0) { wait_syscall = false; continue; } -# endif # endif # ifdef UNIX else if (strcmp(argv[i], "-use_ptrace") == 0) {