diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
commit | 55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch) | |
tree | ecd18b995aea8eeeb8b3823266280d41245bf0f7 /emulator/qtools | |
parent | 82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff) | |
download | sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'emulator/qtools')
46 files changed, 9513 insertions, 0 deletions
diff --git a/emulator/qtools/Android.mk b/emulator/qtools/Android.mk new file mode 100644 index 0000000..afbb3e8 --- /dev/null +++ b/emulator/qtools/Android.mk @@ -0,0 +1,141 @@ +# +# Copyright 2006 The Android Open Source Project +# +# Java method trace dump tool +# + +LOCAL_PATH:= $(call my-dir) + +common_includes := external/qemu +common_cflags := -O0 -g + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := post_trace.cpp trace_reader.cpp decoder.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := post_trace +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := read_trace.cpp trace_reader.cpp decoder.cpp armdis.cpp \ + thumbdis.cpp opcode.cpp read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := read_trace +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := check_trace.cpp trace_reader.cpp decoder.cpp \ + opcode.cpp read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := check_trace +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bb_dump.cpp trace_reader.cpp decoder.cpp \ + read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := bb_dump +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bb2sym.cpp trace_reader.cpp decoder.cpp \ + read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := bb2sym +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := profile_trace.cpp trace_reader.cpp decoder.cpp \ + opcode.cpp read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := profile_trace +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bbprof.cpp trace_reader.cpp decoder.cpp armdis.cpp \ + thumbdis.cpp opcode.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := bbprof +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := q2g.cpp trace_reader.cpp decoder.cpp \ + opcode.cpp read_elf.cpp parse_options.cpp gtrace.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := q2g +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := q2dm.cpp trace_reader.cpp decoder.cpp armdis.cpp \ + thumbdis.cpp opcode.cpp read_elf.cpp parse_options.cpp dmtrace.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := q2dm +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := coverage.cpp trace_reader.cpp decoder.cpp armdis.cpp \ + thumbdis.cpp opcode.cpp read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := coverage +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := stack_dump.cpp trace_reader.cpp decoder.cpp armdis.cpp \ + thumbdis.cpp opcode.cpp read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := stack_dump +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := hist_trace.cpp trace_reader.cpp decoder.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := hist_trace +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := read_addr.cpp trace_reader.cpp decoder.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := read_addr +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := read_pid.cpp trace_reader.cpp decoder.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := read_pid +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := exc_dump.cpp trace_reader.cpp decoder.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := exc_dump +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := read_method.cpp trace_reader.cpp decoder.cpp \ + read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := read_method +include $(BUILD_HOST_EXECUTABLE) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := profile_pid.cpp trace_reader.cpp decoder.cpp \ + read_elf.cpp parse_options.cpp +LOCAL_C_INCLUDES += $(common_includes) +LOCAL_CFLAGS += $(common_cflags) +LOCAL_MODULE := profile_pid +include $(BUILD_HOST_EXECUTABLE) diff --git a/emulator/qtools/armdis.cpp b/emulator/qtools/armdis.cpp new file mode 100644 index 0000000..593f460 --- /dev/null +++ b/emulator/qtools/armdis.cpp @@ -0,0 +1,905 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <string.h> +#include "armdis.h" +#include "opcode.h" + +static char *cond_names[] = { + "eq", + "ne", + "cs", + "cc", + "mi", + "pl", + "vs", + "vc", + "hi", + "ls", + "ge", + "lt", + "gt", + "le", + "", + "RESERVED" +}; + +// Indexed by the shift type (bits 6-5) +static const char *shift_names[] = { + "LSL", + "LSR", + "ASR", + "ROR" +}; + +static char* cond_to_str(int cond) { + return cond_names[cond]; +} + +char *Arm::disasm(uint32_t addr, uint32_t insn, char *result) +{ + static char buf[80]; + char *ptr; + + ptr = result ? result : buf; + Opcode opcode = decode(insn); + switch (opcode) { + case OP_INVALID: + sprintf(ptr, "Invalid"); + return ptr; + case OP_UNDEFINED: + sprintf(ptr, "Undefined"); + return ptr; + case OP_ADC: + case OP_ADD: + case OP_AND: + case OP_BIC: + case OP_CMN: + case OP_CMP: + case OP_EOR: + case OP_MOV: + case OP_MVN: + case OP_ORR: + case OP_RSB: + case OP_RSC: + case OP_SBC: + case OP_SUB: + case OP_TEQ: + case OP_TST: + return disasm_alu(opcode, insn, ptr); + case OP_B: + case OP_BL: + return disasm_branch(addr, opcode, insn, ptr); + case OP_BKPT: + return disasm_bkpt(insn, ptr); + case OP_BLX: + // not supported yet + break; + case OP_BX: + return disasm_bx(insn, ptr); + case OP_CDP: + sprintf(ptr, "cdp"); + return ptr; + case OP_CLZ: + return disasm_clz(insn, ptr); + case OP_LDC: + sprintf(ptr, "ldc"); + return ptr; + case OP_LDM: + case OP_STM: + return disasm_memblock(opcode, insn, ptr); + case OP_LDR: + case OP_LDRB: + case OP_LDRBT: + case OP_LDRT: + case OP_STR: + case OP_STRB: + case OP_STRBT: + case OP_STRT: + return disasm_mem(insn, ptr); + case OP_LDRH: + case OP_LDRSB: + case OP_LDRSH: + case OP_STRH: + return disasm_memhalf(insn, ptr); + case OP_MCR: + case OP_MRC: + return disasm_mcr(opcode, insn, ptr); + case OP_MLA: + return disasm_mla(opcode, insn, ptr); + case OP_MRS: + return disasm_mrs(insn, ptr); + case OP_MSR: + return disasm_msr(insn, ptr); + case OP_MUL: + return disasm_mul(opcode, insn, ptr); + case OP_PLD: + return disasm_pld(insn, ptr); + case OP_STC: + sprintf(ptr, "stc"); + return ptr; + case OP_SWI: + return disasm_swi(insn, ptr); + case OP_SWP: + case OP_SWPB: + return disasm_swp(opcode, insn, ptr); + case OP_UMLAL: + case OP_UMULL: + case OP_SMLAL: + case OP_SMULL: + return disasm_umlal(opcode, insn, ptr); + default: + sprintf(ptr, "Error"); + return ptr; + } + return NULL; +} + +char *Arm::disasm_alu(Opcode opcode, uint32_t insn, char *ptr) +{ + static const uint8_t kNoOperand1 = 1; + static const uint8_t kNoDest = 2; + static const uint8_t kNoSbit = 4; + + char rn_str[20]; + char rd_str[20]; + uint8_t flags = 0; + uint8_t cond = (insn >> 28) & 0xf; + uint8_t is_immed = (insn >> 25) & 0x1; + uint8_t bit_s = (insn >> 20) & 1; + uint8_t rn = (insn >> 16) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint8_t immed = insn & 0xff; + + const char *opname = opcode_names[opcode]; + switch (opcode) { + case OP_CMN: + case OP_CMP: + case OP_TEQ: + case OP_TST: + flags = kNoDest | kNoSbit; + break; + case OP_MOV: + case OP_MVN: + flags = kNoOperand1; + break; + default: + break; + } + + // The "mov" instruction ignores the first operand (rn). + rn_str[0] = 0; + if ((flags & kNoOperand1) == 0) { + sprintf(rn_str, "r%d, ", rn); + } + + // The following instructions do not write the result register (rd): + // tst, teq, cmp, cmn. + rd_str[0] = 0; + if ((flags & kNoDest) == 0) { + sprintf(rd_str, "r%d, ", rd); + } + + char *sbit_str = ""; + if (bit_s && !(flags & kNoSbit)) + sbit_str = "s"; + + if (is_immed) { + sprintf(ptr, "%s%s%s\t%s%s#%u ; 0x%x", + opname, cond_to_str(cond), sbit_str, rd_str, rn_str, immed, immed); + return ptr; + } + + uint8_t shift_is_reg = (insn >> 4) & 1; + uint8_t rotate = (insn >> 8) & 0xf; + uint8_t rm = insn & 0xf; + uint8_t shift_type = (insn >> 5) & 0x3; + uint8_t rs = (insn >> 8) & 0xf; + uint8_t shift_amount = (insn >> 7) & 0x1f; + uint32_t rotated_val = immed; + uint8_t rotate2 = rotate << 1; + rotated_val = (rotated_val >> rotate2) | (rotated_val << (32 - rotate2)); + + if (!shift_is_reg && shift_type == 0 && shift_amount == 0) { + sprintf(ptr, "%s%s%s\t%s%sr%d", + opname, cond_to_str(cond), sbit_str, rd_str, rn_str, rm); + return ptr; + } + + const char *shift_name = shift_names[shift_type]; + if (shift_is_reg) { + sprintf(ptr, "%s%s%s\t%s%sr%d, %s r%d", + opname, cond_to_str(cond), sbit_str, rd_str, rn_str, rm, + shift_name, rs); + return ptr; + } + if (shift_amount == 0) { + if (shift_type == 3) { + sprintf(ptr, "%s%s%s\t%s%sr%d, RRX", + opname, cond_to_str(cond), sbit_str, rd_str, rn_str, rm); + return ptr; + } + shift_amount = 32; + } + sprintf(ptr, "%s%s%s\t%s%sr%d, %s #%u", + opname, cond_to_str(cond), sbit_str, rd_str, rn_str, rm, + shift_name, shift_amount); + return ptr; +} + +char *Arm::disasm_branch(uint32_t addr, Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint32_t offset = insn & 0xffffff; + // Sign-extend the 24-bit offset + if ((offset >> 23) & 1) + offset |= 0xff000000; + + // Pre-compute the left-shift and the prefetch offset + offset <<= 2; + offset += 8; + addr += offset; + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s\t0x%x", opname, cond_to_str(cond), addr); + return ptr; +} + +char *Arm::disasm_bx(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rn = insn & 0xf; + sprintf(ptr, "bx%s\tr%d", cond_to_str(cond), rn); + return ptr; +} + +char *Arm::disasm_bkpt(uint32_t insn, char *ptr) +{ + uint32_t immed = (((insn >> 8) & 0xfff) << 4) | (insn & 0xf); + sprintf(ptr, "bkpt\t#%d", immed); + return ptr; +} + +char *Arm::disasm_clz(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint8_t rm = insn & 0xf; + sprintf(ptr, "clz%s\tr%d, r%d", cond_to_str(cond), rd, rm); + return ptr; +} + +char *Arm::disasm_memblock(Opcode opcode, uint32_t insn, char *ptr) +{ + char tmp_reg[10], tmp_list[80]; + + uint8_t cond = (insn >> 28) & 0xf; + uint8_t write_back = (insn >> 21) & 0x1; + uint8_t bit_s = (insn >> 22) & 0x1; + uint8_t is_up = (insn >> 23) & 0x1; + uint8_t is_pre = (insn >> 24) & 0x1; + uint8_t rn = (insn >> 16) & 0xf; + uint16_t reg_list = insn & 0xffff; + + const char *opname = opcode_names[opcode]; + + char *bang = ""; + if (write_back) + bang = "!"; + + char *carret = ""; + if (bit_s) + carret = "^"; + + char *comma = ""; + tmp_list[0] = 0; + for (int ii = 0; ii < 16; ++ii) { + if (reg_list & (1 << ii)) { + sprintf(tmp_reg, "%sr%d", comma, ii); + strcat(tmp_list, tmp_reg); + comma = ","; + } + } + + char *addr_mode = ""; + if (is_pre) { + if (is_up) { + addr_mode = "ib"; + } else { + addr_mode = "db"; + } + } else { + if (is_up) { + addr_mode = "ia"; + } else { + addr_mode = "da"; + } + } + + sprintf(ptr, "%s%s%s\tr%d%s, {%s}%s", + opname, cond_to_str(cond), addr_mode, rn, bang, tmp_list, carret); + return ptr; +} + +char *Arm::disasm_mem(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t is_reg = (insn >> 25) & 0x1; + uint8_t is_load = (insn >> 20) & 0x1; + uint8_t write_back = (insn >> 21) & 0x1; + uint8_t is_byte = (insn >> 22) & 0x1; + uint8_t is_up = (insn >> 23) & 0x1; + uint8_t is_pre = (insn >> 24) & 0x1; + uint8_t rn = (insn >> 16) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint16_t offset = insn & 0xfff; + + char *opname = "ldr"; + if (!is_load) + opname = "str"; + + char *bang = ""; + if (write_back) + bang = "!"; + + char *minus = ""; + if (is_up == 0) + minus = "-"; + + char *byte = ""; + if (is_byte) + byte = "b"; + + if (is_reg == 0) { + if (is_pre) { + if (offset == 0) { + sprintf(ptr, "%s%s%s\tr%d, [r%d]", + opname, cond_to_str(cond), byte, rd, rn); + } else { + sprintf(ptr, "%s%s%s\tr%d, [r%d, #%s%u]%s", + opname, cond_to_str(cond), byte, rd, rn, minus, offset, bang); + } + } else { + char *transfer = ""; + if (write_back) + transfer = "t"; + sprintf(ptr, "%s%s%s%s\tr%d, [r%d], #%s%u", + opname, cond_to_str(cond), byte, transfer, rd, rn, minus, offset); + } + return ptr; + } + + uint8_t rm = insn & 0xf; + uint8_t shift_type = (insn >> 5) & 0x3; + uint8_t shift_amount = (insn >> 7) & 0x1f; + + const char *shift_name = shift_names[shift_type]; + + if (is_pre) { + if (shift_amount == 0) { + if (shift_type == 0) { + sprintf(ptr, "%s%s%s\tr%d, [r%d, %sr%d]%s", + opname, cond_to_str(cond), byte, rd, rn, minus, rm, bang); + return ptr; + } + if (shift_type == 3) { + sprintf(ptr, "%s%s%s\tr%d, [r%d, %sr%d, RRX]%s", + opname, cond_to_str(cond), byte, rd, rn, minus, rm, bang); + return ptr; + } + shift_amount = 32; + } + sprintf(ptr, "%s%s%s\tr%d, [r%d, %sr%d, %s #%u]%s", + opname, cond_to_str(cond), byte, rd, rn, minus, rm, + shift_name, shift_amount, bang); + return ptr; + } + + char *transfer = ""; + if (write_back) + transfer = "t"; + + if (shift_amount == 0) { + if (shift_type == 0) { + sprintf(ptr, "%s%s%s%s\tr%d, [r%d], %sr%d", + opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm); + return ptr; + } + if (shift_type == 3) { + sprintf(ptr, "%s%s%s%s\tr%d, [r%d], %sr%d, RRX", + opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm); + return ptr; + } + shift_amount = 32; + } + + sprintf(ptr, "%s%s%s%s\tr%d, [r%d], %sr%d, %s #%u", + opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm, + shift_name, shift_amount); + return ptr; +} + +char *Arm::disasm_memhalf(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t is_load = (insn >> 20) & 0x1; + uint8_t write_back = (insn >> 21) & 0x1; + uint8_t is_immed = (insn >> 22) & 0x1; + uint8_t is_up = (insn >> 23) & 0x1; + uint8_t is_pre = (insn >> 24) & 0x1; + uint8_t rn = (insn >> 16) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint8_t bits_65 = (insn >> 5) & 0x3; + uint8_t rm = insn & 0xf; + uint8_t offset = (((insn >> 8) & 0xf) << 4) | (insn & 0xf); + + char *opname = "ldr"; + if (is_load == 0) + opname = "str"; + + char *width = ""; + if (bits_65 == 1) + width = "h"; + else if (bits_65 == 2) + width = "sb"; + else + width = "sh"; + + char *bang = ""; + if (write_back) + bang = "!"; + char *minus = ""; + if (is_up == 0) + minus = "-"; + + if (is_immed) { + if (is_pre) { + if (offset == 0) { + sprintf(ptr, "%s%sh\tr%d, [r%d]", opname, cond_to_str(cond), rd, rn); + } else { + sprintf(ptr, "%s%sh\tr%d, [r%d, #%s%u]%s", + opname, cond_to_str(cond), rd, rn, minus, offset, bang); + } + } else { + sprintf(ptr, "%s%sh\tr%d, [r%d], #%s%u", + opname, cond_to_str(cond), rd, rn, minus, offset); + } + return ptr; + } + + if (is_pre) { + sprintf(ptr, "%s%sh\tr%d, [r%d, %sr%d]%s", + opname, cond_to_str(cond), rd, rn, minus, rm, bang); + } else { + sprintf(ptr, "%s%sh\tr%d, [r%d], %sr%d", + opname, cond_to_str(cond), rd, rn, minus, rm); + } + return ptr; +} + +char *Arm::disasm_mcr(Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t crn = (insn >> 16) & 0xf; + uint8_t crd = (insn >> 12) & 0xf; + uint8_t cpnum = (insn >> 8) & 0xf; + uint8_t opcode2 = (insn >> 5) & 0x7; + uint8_t crm = insn & 0xf; + + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s\t%d, 0, r%d, cr%d, cr%d, {%d}", + opname, cond_to_str(cond), cpnum, crd, crn, crm, opcode2); + return ptr; +} + +char *Arm::disasm_mla(Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rd = (insn >> 16) & 0xf; + uint8_t rn = (insn >> 12) & 0xf; + uint8_t rs = (insn >> 8) & 0xf; + uint8_t rm = insn & 0xf; + uint8_t bit_s = (insn >> 20) & 1; + + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s%s\tr%d, r%d, r%d, r%d", + opname, cond_to_str(cond), bit_s ? "s" : "", rd, rm, rs, rn); + return ptr; +} + +char *Arm::disasm_umlal(Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rdhi = (insn >> 16) & 0xf; + uint8_t rdlo = (insn >> 12) & 0xf; + uint8_t rs = (insn >> 8) & 0xf; + uint8_t rm = insn & 0xf; + uint8_t bit_s = (insn >> 20) & 1; + + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s%s\tr%d, r%d, r%d, r%d", + opname, cond_to_str(cond), bit_s ? "s" : "", rdlo, rdhi, rm, rs); + return ptr; +} + +char *Arm::disasm_mul(Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rd = (insn >> 16) & 0xf; + uint8_t rs = (insn >> 8) & 0xf; + uint8_t rm = insn & 0xf; + uint8_t bit_s = (insn >> 20) & 1; + + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s%s\tr%d, r%d, r%d", + opname, cond_to_str(cond), bit_s ? "s" : "", rd, rm, rs); + return ptr; +} + +char *Arm::disasm_mrs(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint8_t ps = (insn >> 22) & 1; + + sprintf(ptr, "mrs%s\tr%d, %s", cond_to_str(cond), rd, ps ? "spsr" : "cpsr"); + return ptr; +} + +char *Arm::disasm_msr(uint32_t insn, char *ptr) +{ + char flags[8]; + int flag_index = 0; + uint8_t cond = (insn >> 28) & 0xf; + uint8_t is_immed = (insn >> 25) & 0x1; + uint8_t pd = (insn >> 22) & 1; + uint8_t mask = (insn >> 16) & 0xf; + + if (mask & 1) + flags[flag_index++] = 'c'; + if (mask & 2) + flags[flag_index++] = 'x'; + if (mask & 4) + flags[flag_index++] = 's'; + if (mask & 8) + flags[flag_index++] = 'f'; + flags[flag_index] = 0; + + if (is_immed) { + uint32_t immed = insn & 0xff; + uint8_t rotate = (insn >> 8) & 0xf; + uint8_t rotate2 = rotate << 1; + uint32_t rotated_val = (immed >> rotate2) | (immed << (32 - rotate2)); + sprintf(ptr, "msr%s\t%s_%s, #0x%x", + cond_to_str(cond), pd ? "spsr" : "cpsr", flags, rotated_val); + return ptr; + } + + uint8_t rm = insn & 0xf; + + sprintf(ptr, "msr%s\t%s_%s, r%d", + cond_to_str(cond), pd ? "spsr" : "cpsr", flags, rm); + return ptr; +} + +char *Arm::disasm_pld(uint32_t insn, char *ptr) +{ + uint8_t is_reg = (insn >> 25) & 0x1; + uint8_t is_up = (insn >> 23) & 0x1; + uint8_t rn = (insn >> 16) & 0xf; + + char *minus = ""; + if (is_up == 0) + minus = "-"; + + if (is_reg) { + uint8_t rm = insn & 0xf; + sprintf(ptr, "pld\t[r%d, %sr%d]", rn, minus, rm); + return ptr; + } + + uint16_t offset = insn & 0xfff; + if (offset == 0) { + sprintf(ptr, "pld\t[r%d]", rn); + } else { + sprintf(ptr, "pld\t[r%d, #%s%u]", rn, minus, offset); + } + return ptr; +} + +char *Arm::disasm_swi(uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint32_t sysnum = insn & 0x00ffffff; + + sprintf(ptr, "swi%s 0x%x", cond_to_str(cond), sysnum); + return ptr; +} + +char *Arm::disasm_swp(Opcode opcode, uint32_t insn, char *ptr) +{ + uint8_t cond = (insn >> 28) & 0xf; + uint8_t rn = (insn >> 16) & 0xf; + uint8_t rd = (insn >> 12) & 0xf; + uint8_t rm = insn & 0xf; + + const char *opname = opcode_names[opcode]; + sprintf(ptr, "%s%s\tr%d, r%d, [r%d]", opname, cond_to_str(cond), rd, rm, rn); + return ptr; +} + +Opcode Arm::decode(uint32_t insn) { + uint32_t bits27_26 = (insn >> 26) & 0x3; + switch (bits27_26) { + case 0x0: + return decode00(insn); + case 0x1: + return decode01(insn); + case 0x2: + return decode10(insn); + case 0x3: + return decode11(insn); + } + return OP_INVALID; +} + +Opcode Arm::decode00(uint32_t insn) { + uint8_t bit25 = (insn >> 25) & 0x1; + uint8_t bit4 = (insn >> 4) & 0x1; + if (bit25 == 0 && bit4 == 1) { + if ((insn & 0x0ffffff0) == 0x012fff10) { + // Bx instruction + return OP_BX; + } + if ((insn & 0x0ff000f0) == 0x01600010) { + // Clz instruction + return OP_CLZ; + } + if ((insn & 0xfff000f0) == 0xe1200070) { + // Bkpt instruction + return OP_BKPT; + } + uint32_t bits7_4 = (insn >> 4) & 0xf; + if (bits7_4 == 0x9) { + if ((insn & 0x0ff00ff0) == 0x01000090) { + // Swp instruction + uint8_t bit22 = (insn >> 22) & 0x1; + if (bit22) + return OP_SWPB; + return OP_SWP; + } + // One of the multiply instructions + return decode_mul(insn); + } + + uint8_t bit7 = (insn >> 7) & 0x1; + if (bit7 == 1) { + // One of the load/store halfword/byte instructions + return decode_ldrh(insn); + } + } + + // One of the data processing instructions + return decode_alu(insn); +} + +Opcode Arm::decode01(uint32_t insn) { + uint8_t is_reg = (insn >> 25) & 0x1; + uint8_t bit4 = (insn >> 4) & 0x1; + if (is_reg == 1 && bit4 == 1) + return OP_UNDEFINED; + uint8_t is_load = (insn >> 20) & 0x1; + uint8_t is_byte = (insn >> 22) & 0x1; + if ((insn & 0xfd70f000) == 0xf550f000) { + // Pre-load + return OP_PLD; + } + if (is_load) { + if (is_byte) { + // Load byte + return OP_LDRB; + } + // Load word + return OP_LDR; + } + if (is_byte) { + // Store byte + return OP_STRB; + } + // Store word + return OP_STR; +} + +Opcode Arm::decode10(uint32_t insn) { + uint8_t bit25 = (insn >> 25) & 0x1; + if (bit25 == 0) { + // LDM/STM + uint8_t is_load = (insn >> 20) & 0x1; + if (is_load) + return OP_LDM; + return OP_STM; + } + // Branch or Branch with link + uint8_t is_link = (insn >> 24) & 1; + uint32_t offset = insn & 0xffffff; + + // Sign-extend the 24-bit offset + if ((offset >> 23) & 1) + offset |= 0xff000000; + + // Pre-compute the left-shift and the prefetch offset + offset <<= 2; + offset += 8; + if (is_link == 0) + return OP_B; + return OP_BL; +} + +Opcode Arm::decode11(uint32_t insn) { + uint8_t bit25 = (insn >> 25) & 0x1; + if (bit25 == 0) { + // LDC, SDC + uint8_t is_load = (insn >> 20) & 0x1; + if (is_load) { + // LDC + return OP_LDC; + } + // STC + return OP_STC; + } + + uint8_t bit24 = (insn >> 24) & 0x1; + if (bit24 == 0x1) { + // SWI + return OP_SWI; + } + + uint8_t bit4 = (insn >> 4) & 0x1; + uint8_t cpnum = (insn >> 8) & 0xf; + + if (cpnum == 15) { + // Special case for coprocessor 15 + uint8_t opcode = (insn >> 21) & 0x7; + if (bit4 == 0 || opcode != 0) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + + // MRC, MCR + uint8_t is_mrc = (insn >> 20) & 0x1; + if (is_mrc) + return OP_MRC; + return OP_MCR; + } + + if (bit4 == 0) { + // CDP + return OP_CDP; + } + // MRC, MCR + uint8_t is_mrc = (insn >> 20) & 0x1; + if (is_mrc) + return OP_MRC; + return OP_MCR; +} + +Opcode Arm::decode_mul(uint32_t insn) { + uint8_t bit24 = (insn >> 24) & 0x1; + if (bit24 != 0) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + uint8_t bit23 = (insn >> 23) & 0x1; + uint8_t bit22_U = (insn >> 22) & 0x1; + uint8_t bit21_A = (insn >> 21) & 0x1; + if (bit23 == 0) { + // 32-bit multiply + if (bit22_U != 0) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + if (bit21_A == 0) + return OP_MUL; + return OP_MLA; + } + // 64-bit multiply + if (bit22_U == 0) { + // Unsigned multiply long + if (bit21_A == 0) + return OP_UMULL; + return OP_UMLAL; + } + // Signed multiply long + if (bit21_A == 0) + return OP_SMULL; + return OP_SMLAL; +} + +Opcode Arm::decode_ldrh(uint32_t insn) { + uint8_t is_load = (insn >> 20) & 0x1; + uint8_t bits_65 = (insn >> 5) & 0x3; + if (is_load) { + if (bits_65 == 0x1) { + // Load unsigned halfword + return OP_LDRH; + } else if (bits_65 == 0x2) { + // Load signed byte + return OP_LDRSB; + } + // Signed halfword + if (bits_65 != 0x3) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + // Load signed halfword + return OP_LDRSH; + } + // Store halfword + if (bits_65 != 0x1) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + // Store halfword + return OP_STRH; +} + +Opcode Arm::decode_alu(uint32_t insn) { + uint8_t is_immed = (insn >> 25) & 0x1; + uint8_t opcode = (insn >> 21) & 0xf; + uint8_t bit_s = (insn >> 20) & 1; + uint8_t shift_is_reg = (insn >> 4) & 1; + uint8_t bit7 = (insn >> 7) & 1; + if (!is_immed && shift_is_reg && (bit7 != 0)) { + // This is an unexpected bit pattern. Create an undefined + // instruction in case this is ever executed. + return OP_UNDEFINED; + } + switch (opcode) { + case 0x0: + return OP_AND; + case 0x1: + return OP_EOR; + case 0x2: + return OP_SUB; + case 0x3: + return OP_RSB; + case 0x4: + return OP_ADD; + case 0x5: + return OP_ADC; + case 0x6: + return OP_SBC; + case 0x7: + return OP_RSC; + case 0x8: + if (bit_s) + return OP_TST; + return OP_MRS; + case 0x9: + if (bit_s) + return OP_TEQ; + return OP_MSR; + case 0xa: + if (bit_s) + return OP_CMP; + return OP_MRS; + case 0xb: + if (bit_s) + return OP_CMN; + return OP_MSR; + case 0xc: + return OP_ORR; + case 0xd: + return OP_MOV; + case 0xe: + return OP_BIC; + case 0xf: + return OP_MVN; + } + // Unreachable + return OP_INVALID; +} diff --git a/emulator/qtools/armdis.h b/emulator/qtools/armdis.h new file mode 100644 index 0000000..230f833 --- /dev/null +++ b/emulator/qtools/armdis.h @@ -0,0 +1,45 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef ARMDIS_H +#define ARMDIS_H + +#include <inttypes.h> +#include "opcode.h" + +class Arm { + public: + static char *disasm(uint32_t addr, uint32_t insn, char *buffer); + static Opcode decode(uint32_t insn); + + private: + static Opcode decode00(uint32_t insn); + static Opcode decode01(uint32_t insn); + static Opcode decode10(uint32_t insn); + static Opcode decode11(uint32_t insn); + static Opcode decode_mul(uint32_t insn); + static Opcode decode_ldrh(uint32_t insn); + static Opcode decode_alu(uint32_t insn); + + static char *disasm_alu(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_branch(uint32_t addr, Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_bx(uint32_t insn, char *ptr); + static char *disasm_bkpt(uint32_t insn, char *ptr); + static char *disasm_clz(uint32_t insn, char *ptr); + static char *disasm_memblock(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_mem(uint32_t insn, char *ptr); + static char *disasm_memhalf(uint32_t insn, char *ptr); + static char *disasm_mcr(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_mla(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_umlal(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_mul(Opcode opcode, uint32_t insn, char *ptr); + static char *disasm_mrs(uint32_t insn, char *ptr); + static char *disasm_msr(uint32_t insn, char *ptr); + static char *disasm_pld(uint32_t insn, char *ptr); + static char *disasm_swi(uint32_t insn, char *ptr); + static char *disasm_swp(Opcode opcode, uint32_t insn, char *ptr); +}; + +extern char *disasm_insn_thumb(uint32_t pc, uint32_t insn1, uint32_t insn2, char *result); +extern Opcode decode_insn_thumb(uint32_t given); + +#endif /* ARMDIS_H */ diff --git a/emulator/qtools/bb2sym.cpp b/emulator/qtools/bb2sym.cpp new file mode 100644 index 0000000..8a18b67 --- /dev/null +++ b/emulator/qtools/bb2sym.cpp @@ -0,0 +1,140 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +struct MyStaticRec { + StaticRec bb; + symbol_type *sym; + MyStaticRec *inner; // pointer to an inner basic block + int is_thumb; +}; + +MyStaticRec **assign_inner_blocks(int num_blocks, MyStaticRec *blocks); + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file\n", program); + OptionsUsage(); +} + +// This function is called from quicksort to compare addresses of basic +// blocks. +int cmp_inc_addr(const void *a, const void *b) { + MyStaticRec *bb1, *bb2; + + bb1 = *(MyStaticRec**)a; + bb2 = *(MyStaticRec**)b; + if (bb1->bb.bb_addr < bb2->bb.bb_addr) + return -1; + if (bb1->bb.bb_addr > bb2->bb.bb_addr) + return 1; + return bb1->bb.bb_num - bb2->bb.bb_num; +} + +int main(int argc, char **argv) { + uint32_t insns[kMaxInsnPerBB]; + + // Parse the options + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<> *trace = new TraceReader<>; + trace->Open(trace_filename); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + TraceHeader *header = trace->GetHeader(); + uint32_t num_static_bb = header->num_static_bb; + + // Allocate space for all of the static blocks + MyStaticRec *blocks = new MyStaticRec[num_static_bb]; + + // Read in all the static blocks + for (uint32_t ii = 0; ii < num_static_bb; ++ii) { + trace->ReadStatic(&blocks[ii].bb); + blocks[ii].is_thumb = blocks[ii].bb.bb_addr & 1; + blocks[ii].bb.bb_addr &= ~1; + blocks[ii].sym = NULL; + blocks[ii].inner = NULL; + trace->ReadStaticInsns(blocks[ii].bb.num_insns, insns); + } + + MyStaticRec **sorted = assign_inner_blocks(num_static_bb, blocks); + + while (1) { + symbol_type *sym; + BBEvent event; + BBEvent ignored; + + if (GetNextValidEvent(trace, &event, &ignored, &sym)) + break; + + uint64_t bb_num = event.bb_num; + blocks[bb_num].sym = sym; + } + + printf("# bb num_insns bb_addr file symbol\n"); + for (uint32_t ii = 0; ii < num_static_bb; ++ii) { + if (sorted[ii]->bb.bb_addr == 0 || sorted[ii]->bb.num_insns == 0 + || sorted[ii]->sym == NULL) + continue; + + printf("%8lld %3d 0x%08x %s %s\n", + sorted[ii]->bb.bb_num, sorted[ii]->bb.num_insns, + sorted[ii]->bb.bb_addr, sorted[ii]->sym->region->path, + sorted[ii]->sym->name); + } + return 0; +} + +// Find the basic blocks that are subsets of other basic blocks. +MyStaticRec **assign_inner_blocks(int num_blocks, MyStaticRec *blocks) +{ + int ii; + uint32_t addr_end, addr_diff; + + // Create a list of pointers to the basic blocks that we can sort. + MyStaticRec **sorted = new MyStaticRec*[num_blocks]; + for (ii = 0; ii < num_blocks; ++ii) { + sorted[ii] = &blocks[ii]; + } + + // Sort the basic blocks into increasing address order + qsort(sorted, num_blocks, sizeof(MyStaticRec*), cmp_inc_addr); + + // Create pointers to inner blocks and break up the enclosing block + // so that there is no overlap. + for (ii = 0; ii < num_blocks - 1; ++ii) { + int num_bytes; + if (sorted[ii]->is_thumb) + num_bytes = sorted[ii]->bb.num_insns << 1; + else + num_bytes = sorted[ii]->bb.num_insns << 2; + addr_end = sorted[ii]->bb.bb_addr + num_bytes; + if (addr_end > sorted[ii + 1]->bb.bb_addr) { + sorted[ii]->inner = sorted[ii + 1]; + addr_diff = sorted[ii + 1]->bb.bb_addr - sorted[ii]->bb.bb_addr; + uint32_t num_insns; + if (sorted[ii]->is_thumb) + num_insns = addr_diff >> 1; + else + num_insns = addr_diff >> 2; + sorted[ii]->bb.num_insns = num_insns; + } + } + + return sorted; +} diff --git a/emulator/qtools/bb_dump.cpp b/emulator/qtools/bb_dump.cpp new file mode 100644 index 0000000..de241fb --- /dev/null +++ b/emulator/qtools/bb_dump.cpp @@ -0,0 +1,47 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file\n", program); + OptionsUsage(); +} + +int main(int argc, char **argv) { + // Parse the options + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<> *trace = new TraceReader<>; + trace->Open(trace_filename); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + printf("# time bb pid num_insns bb_addr\n"); + while (1) { + symbol_type *sym; + BBEvent event; + BBEvent ignored; + + if (GetNextValidEvent(trace, &event, &ignored, &sym)) + break; + printf("%7lld %4lld %5d %3d 0x%08x %s\n", + event.time, event.bb_num, event.pid, event.num_insns, + event.bb_addr, sym->name); + } + return 0; +} diff --git a/emulator/qtools/bbprof.cpp b/emulator/qtools/bbprof.cpp new file mode 100644 index 0000000..36d0941 --- /dev/null +++ b/emulator/qtools/bbprof.cpp @@ -0,0 +1,222 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "trace_reader.h" +#include "armdis.h" + +struct MyStaticRec { + StaticRec bb; + uint32_t *insns; + uint32_t *cycles; // number of cycles for each insn + uint32_t elapsed; // number of cycles for basic block + int freq; // execution frequency + MyStaticRec *inner; // pointer to an inner basic block + int is_thumb; +}; + +MyStaticRec **assign_inner_blocks(int num_blocks, MyStaticRec *blocks); + +// This function is called from quicksort to compare addresses of basic +// blocks. +int cmp_inc_addr(const void *a, const void *b) { + MyStaticRec *bb1, *bb2; + + bb1 = *(MyStaticRec**)a; + bb2 = *(MyStaticRec**)b; + if (bb1->bb.bb_addr < bb2->bb.bb_addr) + return -1; + if (bb1->bb.bb_addr > bb2->bb.bb_addr) + return 1; + return bb1->bb.bb_num - bb2->bb.bb_num; +} + +// This function is called from quicksort to compare the elapsed time +// of basic blocks. +int cmp_dec_elapsed(const void *a, const void *b) { + MyStaticRec *bb1, *bb2; + + bb1 = *(MyStaticRec**)a; + bb2 = *(MyStaticRec**)b; + if (bb1->elapsed < bb2->elapsed) + return 1; + if (bb1->elapsed > bb2->elapsed) + return -1; + return bb1->bb.bb_num - bb2->bb.bb_num; +} + +// This function is called from quicksort to compare frequencies of +// basic blocks. +int cmp_dec_freq(const void *a, const void *b) { + MyStaticRec *bb1, *bb2; + + bb1 = *(MyStaticRec**)a; + bb2 = *(MyStaticRec**)b; + if (bb1->freq < bb2->freq) + return 1; + if (bb1->freq > bb2->freq) + return -1; + return bb1->bb.bb_num - bb2->bb.bb_num; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->Open(trace_filename); + TraceHeader *header = trace->GetHeader(); + uint32_t num_static_bb = header->num_static_bb; + + // Allocate space for all of the static blocks + MyStaticRec *blocks = new MyStaticRec[num_static_bb]; + + // Read in all the static blocks + for (uint32_t ii = 0; ii < num_static_bb; ++ii) { + trace->ReadStatic(&blocks[ii].bb); + blocks[ii].is_thumb = blocks[ii].bb.bb_addr & 1; + blocks[ii].bb.bb_addr &= ~1; + uint32_t num_insns = blocks[ii].bb.num_insns; + blocks[ii].insns = new uint32_t[num_insns]; + blocks[ii].cycles = new uint32_t[num_insns]; + memset(blocks[ii].cycles, 0, num_insns * sizeof(uint32_t)); + trace->ReadStaticInsns(num_insns, blocks[ii].insns); + blocks[ii].elapsed = 0; + blocks[ii].freq = 0; + blocks[ii].inner = NULL; + } + + MyStaticRec **sorted = assign_inner_blocks(num_static_bb, blocks); + + uint32_t prev_time = 0; + uint32_t elapsed = 0; + uint32_t dummy; + uint32_t *cycle_ptr = &dummy; + uint32_t *bb_elapsed_ptr = &dummy; + while (1) { + BBEvent event; + + if (trace->ReadBB(&event)) + break; + // Assign frequencies to each basic block + uint64_t bb_num = event.bb_num; + int num_insns = event.num_insns; + blocks[bb_num].freq += 1; + for (MyStaticRec *bptr = blocks[bb_num].inner; bptr; bptr = bptr->inner) + bptr->freq += 1; + + // Assign simulation time to each instruction + for (MyStaticRec *bptr = &blocks[bb_num]; bptr; bptr = bptr->inner) { + uint32_t bb_num_insns = bptr->bb.num_insns; + for (uint32_t ii = 0; num_insns && ii < bb_num_insns; ++ii, --num_insns) { + uint32_t sim_time = trace->ReadInsnTime(event.time); + elapsed = sim_time - prev_time; + prev_time = sim_time; + + // Attribute the elapsed time to the previous instruction and + // basic block. + *cycle_ptr += elapsed; + *bb_elapsed_ptr += elapsed; + cycle_ptr = &bptr->cycles[ii]; + bb_elapsed_ptr = &bptr->elapsed; + } + } + } + *cycle_ptr += 1; + *bb_elapsed_ptr += 1; + + // Sort the basic blocks into decreasing elapsed time + qsort(sorted, num_static_bb, sizeof(MyStaticRec*), cmp_dec_elapsed); + + char spaces[80]; + memset(spaces, ' ', 79); + spaces[79] = 0; + for (uint32_t ii = 0; ii < num_static_bb; ++ii) { + printf("bb %lld addr: 0x%x, insns: %d freq: %u elapsed: %u\n", + sorted[ii]->bb.bb_num, sorted[ii]->bb.bb_addr, + sorted[ii]->bb.num_insns, sorted[ii]->freq, + sorted[ii]->elapsed); + int num_insns = sorted[ii]->bb.num_insns; + uint32_t addr = sorted[ii]->bb.bb_addr; + for (int jj = 0; jj < num_insns; ++jj) { + uint32_t elapsed = sorted[ii]->cycles[jj]; + uint32_t insn = sorted[ii]->insns[jj]; + if (insn_is_thumb(insn)) { + insn = insn_unwrap_thumb(insn); + + // thumb_pair is true if this is the first of a pair of + // thumb instructions (BL or BLX). + bool thumb_pair = ((insn & 0xf800) == 0xf000); + + // Get the next thumb instruction (if any) because we may need + // it for the case where insn is BL or BLX. + uint32_t insn2 = 0; + if (thumb_pair && (jj + 1 < num_insns)) { + insn2 = sorted[ii]->insns[jj + 1]; + insn2 = insn_unwrap_thumb(insn2); + jj += 1; + } + char *disasm = disasm_insn_thumb(addr, insn, insn2, NULL); + if (thumb_pair) { + printf(" %4u %08x %04x %04x %s\n", elapsed, addr, insn, + insn2, disasm); + addr += 2; + } else { + printf(" %4u %08x %04x %s\n", elapsed, addr, insn, + disasm); + } + addr += 2; + } else { + char *disasm = Arm::disasm(addr, insn, NULL); + printf(" %4u %08x %08x %s\n", elapsed, addr, insn, disasm); + addr += 4; + } + } + } + + delete[] sorted; + return 0; +} + +// Find the basic blocks that are subsets of other basic blocks. +MyStaticRec **assign_inner_blocks(int num_blocks, MyStaticRec *blocks) +{ + int ii; + uint32_t addr_end, addr_diff; + + // Create a list of pointers to the basic blocks that we can sort. + MyStaticRec **sorted = new MyStaticRec*[num_blocks]; + for (ii = 0; ii < num_blocks; ++ii) { + sorted[ii] = &blocks[ii]; + } + + // Sort the basic blocks into increasing address order + qsort(sorted, num_blocks, sizeof(MyStaticRec*), cmp_inc_addr); + + // Create pointers to inner blocks and break up the enclosing block + // so that there is no overlap. + for (ii = 0; ii < num_blocks - 1; ++ii) { + int num_bytes; + if (sorted[ii]->is_thumb) + num_bytes = sorted[ii]->bb.num_insns << 1; + else + num_bytes = sorted[ii]->bb.num_insns << 2; + addr_end = sorted[ii]->bb.bb_addr + num_bytes; + if (addr_end > sorted[ii + 1]->bb.bb_addr) { + sorted[ii]->inner = sorted[ii + 1]; + addr_diff = sorted[ii + 1]->bb.bb_addr - sorted[ii]->bb.bb_addr; + uint32_t num_insns; + if (sorted[ii]->is_thumb) + num_insns = addr_diff >> 1; + else + num_insns = addr_diff >> 2; + sorted[ii]->bb.num_insns = num_insns; + } + } + + return sorted; +} diff --git a/emulator/qtools/bitvector.h b/emulator/qtools/bitvector.h new file mode 100644 index 0000000..d3f5cf1 --- /dev/null +++ b/emulator/qtools/bitvector.h @@ -0,0 +1,40 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef BITVECTOR_H +#define BITVECTOR_H + +#include <inttypes.h> +#include <assert.h> + +class Bitvector { + public: + explicit Bitvector(int num_bits) { + num_bits_ = num_bits; + + // Round up to a multiple of 32 + num_bits = (num_bits + 31) & ~31; + vector_ = new uint32_t[num_bits >> 5]; + } + ~Bitvector() { + delete[] vector_; + } + + void SetBit(int bitnum) { + assert(bitnum < num_bits_); + vector_[bitnum >> 5] |= 1 << (bitnum & 31); + } + void ClearBit(int bitnum) { + assert(bitnum < num_bits_); + vector_[bitnum >> 5] &= ~(1 << (bitnum & 31)); + } + bool GetBit(int bitnum) { + assert(bitnum < num_bits_); + return (vector_[bitnum >> 5] >> (bitnum & 31)) & 1; + } + + private: + int num_bits_; + uint32_t *vector_; +}; + +#endif // BITVECTOR_H diff --git a/emulator/qtools/callstack.h b/emulator/qtools/callstack.h new file mode 100644 index 0000000..b869956 --- /dev/null +++ b/emulator/qtools/callstack.h @@ -0,0 +1,760 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef CALL_STACK_H +#define CALL_STACK_H + +#include "opcode.h" +#include "armdis.h" + +class CallStackBase { + public: + int getId() { return mId; } + void setId(int id) { mId = id; } + + private: + int mId; +}; + +// Define a template class for the stack frame. The template parameter +// SYM is the symbol_type from the TraceReader<> template class. To +// use the CallStack class, the user derives a subclass of StackFrame +// and defines push() and pop() methods. This derived class is then +// passed as a template parameter to CallStack. +template <class SYM> +class StackFrame { + public: + + virtual ~StackFrame() {}; + + virtual void push(int stackLevel, uint64_t time, CallStackBase *base) {}; + virtual void pop(int stackLevel, uint64_t time, CallStackBase *base) {}; + + typedef SYM symbol_type; + static const uint32_t kCausedException = 0x01; + static const uint32_t kInterpreted = 0x02; + static const uint32_t kPopBarrier = (kCausedException | kInterpreted); + + symbol_type *function; // the symbol for the function we entered + uint32_t addr; // return address when this function returns + uint32_t flags; + uint32_t time; // for debugging when a problem occurred + uint32_t global_time; // for debugging when a problem occurred +}; + +template <class FRAME, class BASE = CallStackBase> +class CallStack : public BASE { + public: + typedef typename FRAME::symbol_type symbol_type; + typedef typename FRAME::symbol_type::region_type region_type; + typedef BASE base_type; + + CallStack(int id, int numFrames, TraceReaderType *trace); + ~CallStack(); + + void updateStack(BBEvent *event, symbol_type *function); + void popAll(uint64_t time); + void threadStart(uint64_t time); + void threadStop(uint64_t time); + + // Set to true if you don't want to see any Java methods + void setNativeOnly(bool nativeOnly) { + mNativeOnly = nativeOnly; + } + + int getStackLevel() { return mTop; } + + uint64_t getGlobalTime(uint64_t time) { return time + mSkippedTime; } + void showStack(); + void showSnapshotStack(); + + private: + enum Action { NONE, PUSH, POP }; + + Action getAction(BBEvent *event, symbol_type *function); + Action getMethodAction(BBEvent *event, symbol_type *function); + void doSimplePush(symbol_type *function, uint32_t addr, + uint64_t time); + void doSimplePop(uint64_t time); + void doPush(BBEvent *event, symbol_type *function); + void doPop(BBEvent *event, symbol_type *function, Action methodAction); + + void transitionToJava(); + void transitionFromJava(uint64_t time); + + TraceReaderType *mTrace; + bool mNativeOnly; + + symbol_type mDummyFunction; + region_type mDummyRegion; + + int mNumFrames; + FRAME *mFrames; + int mTop; // index of the next stack frame to write + + int mJavaTop; + + int mSnapshotNumFrames; + FRAME *mSnapshotFrames; + int mSnapshotTop; // index of the next stack frame to write + + symbol_type *mPrevFunction; + BBEvent mPrevEvent; + + symbol_type *mUserFunction; + BBEvent mUserEvent; // the previous user-mode event + + uint64_t mSkippedTime; + uint64_t mLastRunTime; + + static MethodRec sCurrentMethod; + static MethodRec sNextMethod; +}; + +template<class FRAME, class BASE> +MethodRec CallStack<FRAME, BASE>::sCurrentMethod; +template<class FRAME, class BASE> +MethodRec CallStack<FRAME, BASE>::sNextMethod; + +template<class FRAME, class BASE> +CallStack<FRAME, BASE>::CallStack(int id, int numFrames, TraceReaderType *trace) +{ + mNativeOnly = false; + mTrace = trace; + BASE::setId(id); + mNumFrames = numFrames; + mFrames = new FRAME[mNumFrames]; + mTop = 0; + + mSnapshotNumFrames = numFrames; + mSnapshotFrames = new FRAME[mSnapshotNumFrames]; + mSnapshotTop = 0; + + memset(&mDummyFunction, 0, sizeof(symbol_type)); + memset(&mDummyRegion, 0, sizeof(region_type)); + mDummyFunction.region = &mDummyRegion; + mPrevFunction = &mDummyFunction; + memset(&mPrevEvent, 0, sizeof(BBEvent)); + mUserFunction = &mDummyFunction; + memset(&mUserEvent, 0, sizeof(BBEvent)); + mSkippedTime = 0; + mLastRunTime = 0; + mJavaTop = 0; + + // Read the first two methods from the trace if we haven't already read + // from the method trace yet. + if (sCurrentMethod.time == 0) { + if (mTrace->ReadMethod(&sCurrentMethod)) { + sCurrentMethod.time = ~0ull; + sNextMethod.time = ~0ull; + } + if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) { + sNextMethod.time = ~0ull; + } + } +} + +template<class FRAME, class BASE> +CallStack<FRAME, BASE>::~CallStack() +{ + delete mFrames; +} + +template<class FRAME, class BASE> +void +CallStack<FRAME, BASE>::updateStack(BBEvent *event, symbol_type *function) +{ + if (mNativeOnly) { + // If this is an interpreted function, then use the native VM function + // instead. + if (function->vm_sym != NULL) + function = function->vm_sym; + } + + Action action = getAction(event, function); + Action methodAction = getMethodAction(event, function); + + // Pop off native functions before pushing or popping Java methods. + if (action == POP && mPrevFunction->vm_sym == NULL) { + // Pop off the previous function first. + doPop(event, function, NONE); + if (methodAction == POP) { + doPop(event, function, POP); + } else if (methodAction == PUSH) { + doPush(event, function); + } + } else { + if (methodAction != NONE) { + // If the method trace has a push or pop, then do it. + action = methodAction; + } else if (function->vm_sym != NULL) { + // This function is a Java method. Don't push or pop the + // Java method without a corresponding method trace record. + action = NONE; + } + if (action == POP) { + doPop(event, function, methodAction); + } else if (action == PUSH) { + doPush(event, function); + } + } + + // If the stack is now empty, then push the current function. + if (mTop == 0) { + uint64_t time = event->time - mSkippedTime; + doSimplePush(function, 0, time); + } + + mPrevFunction = function; + mPrevEvent = *event; +} + +template<class FRAME, class BASE> +void +CallStack<FRAME, BASE>::threadStart(uint64_t time) +{ + mSkippedTime += time - mLastRunTime; +} + +template<class FRAME, class BASE> +void +CallStack<FRAME, BASE>::threadStop(uint64_t time) +{ + mLastRunTime = time; +} + +template<class FRAME, class BASE> +typename CallStack<FRAME, BASE>::Action +CallStack<FRAME, BASE>::getAction(BBEvent *event, symbol_type *function) +{ + Action action; + uint32_t offset; + + // Compute the offset from the start of the function to this basic + // block address. + offset = event->bb_addr - function->addr - function->region->base_addr; + + // Get the previously executed instruction + Opcode op = OP_INVALID; + int numInsns = mPrevEvent.num_insns; + uint32_t insn = 0; + if (numInsns > 0) { + insn = mPrevEvent.insns[numInsns - 1]; + if (mPrevEvent.is_thumb) { + insn = insn_unwrap_thumb(insn); + op = decode_insn_thumb(insn); + } else { + op = Arm::decode(insn); + } + } + + // The number of bytes in the previous basic block depends on + // whether the basic block was ARM or THUMB instructions. + int numBytes; + if (mPrevEvent.is_thumb) { + numBytes = numInsns << 1; + } else { + numBytes = numInsns << 2; + } + + // If this basic block follows the previous one, then return NONE. + // If we don't do this, then we may be fooled into thinking this + // is a POP if the previous block ended with a conditional + // (non-executed) ldmia instruction. We do this check before + // checking if we are in a different function because we otherwise + // we might be fooled into thinking this is a PUSH to a new function + // when it is really just a fall-thru into a local kernel symbol + // that just looks like a new function. + uint32_t prev_end_addr = mPrevEvent.bb_addr + numBytes; + if (prev_end_addr == event->bb_addr) { + return NONE; + } + + // If this basic block is in the same function as the last basic block, + // then just return NONE (but see the exceptions below). + // Exception 1: if the function calls itself (offset == 0) then we + // want to push this function. + // Exception 2: if the function returns to itself, then we want + // to pop this function. We detect this case by checking if the last + // instruction in the previous basic block was a load-multiple (ldm) + // and included r15 as one of the loaded registers. + if (function == mPrevFunction) { + if (numInsns > 0) { + // If this is the beginning of the function and the previous + // instruction was not a branch, then it's a PUSH. + if (offset == 0 && op != OP_B && op != OP_THUMB_B) + return PUSH; + + // If the previous instruction was an ldm that loaded r15, + // then it's a POP. + if (offset != 0 && ((op == OP_LDM && (insn & 0x8000)) + || (op == OP_THUMB_POP && (insn & 0x100)))) { + return POP; + } + } + + return NONE; + } + + // We have to figure out if this new function is a call or a + // return. We don't necessarily have a complete call stack (since + // we could have started tracing at any point), so we have to use + // heuristics. If the address we are jumping to is the beginning + // of a function, or if the instruction that took us there was + // either "bl" or "blx" then this is a PUSH. Also, if the + // function offset is non-zero and the previous instruction is a + // branch instruction, we will call it a PUSH. This happens in + // the kernel a lot when there is a branch to an offset from a + // label. A couple more special cases: + // + // - entering a .plt section ("procedure linkage table") is a PUSH, + // - an exception that jumps into the kernel vector entry point + // is also a push. + // + // If the function offset is non-zero and the previous instruction + // is a bx or some non-branch instruction, then it's a POP. + // + // There's another special case that comes up. The user code + // might execute an instruction that returns but before the pc + // starts executing in the caller, a kernel interrupt occurs. + // But it may be hard to tell if this is a return until after + // the kernel interrupt code is done and returns to user space. + // So we save the last user basic block and look at it when + // we come back into user space. + + const uint32_t kIsKernelRegion = region_type::kIsKernelRegion; + + if (((mPrevFunction->region->flags & kIsKernelRegion) == 0) + && (function->region->flags & kIsKernelRegion)) { + // We just switched into the kernel. Save the previous + // user-mode basic block and function. + mUserEvent = mPrevEvent; + mUserFunction = mPrevFunction; + } else if ((mPrevFunction->region->flags & kIsKernelRegion) + && ((function->region->flags & kIsKernelRegion) == 0)) { + // We just switched from kernel to user mode. + return POP; + } + + action = PUSH; + if (offset != 0 && mPrevFunction != &mDummyFunction) { + // We are jumping into the middle of a function, so this is + // probably a return, not a function call. But look at the + // previous instruction first to see if it was a branch-and-link. + + // If the previous instruction was not a branch (and not a + // branch-and-link) then POP; or if it is a "bx" instruction + // then POP because that is used to return from functions. + if (!isBranch(op) || op == OP_BX || op == OP_THUMB_BX) { + action = POP; + } else if (isBranch(op) && !isBranchLink(op)) { + // If the previous instruction was a normal branch to a + // local symbol then don't count it as a push or a pop. + action = NONE; + } + + if (function->flags & symbol_type::kIsVectorTable) + action = PUSH; + } + return action; +} + + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::doPush(BBEvent *event, symbol_type *function) +{ + uint64_t time = event->time - mSkippedTime; + + // Check for stack overflow + if (mTop >= mNumFrames) { +#if 0 + showStack(); +#endif + fprintf(stderr, "Error: stack overflow (%d frames)\n", mTop); + exit(1); + } + + // Compute the return address here because we may need to change + // it if we are popping off a frame for a vector table. + int numBytes; + if (mPrevEvent.is_thumb) { + numBytes = mPrevEvent.num_insns << 1; + } else { + numBytes = mPrevEvent.num_insns << 2; + } + uint32_t retAddr = mPrevEvent.bb_addr + numBytes; + + // If this is a Java method then set the return address to zero. + // We won't be using it for popping the method and it may lead + // to false matches when searching the stack. + if (function->vm_sym != NULL) { + retAddr = 0; + } + +#if 0 + if (function->flags & symbol_type::kIsVectorStart) { + printf("stack before entering exception\n"); + showStack(); + } +#endif + + // If the previous function was a vector table, then pop it + // off before pushing on the new function. Also, change the + // return address for the new function to the return address + // from the vector table. + if ((mPrevFunction->flags & symbol_type::kIsVectorTable) && mTop > 0) { + retAddr = mFrames[mTop - 1].addr; + doSimplePop(time); + } + + const uint32_t kIsKernelRegion = region_type::kIsKernelRegion; + + // The following code handles the case where one function, F1, + // calls another function, F2, but the before F2 can start + // executing, it takes a page fault (on the first instruction + // in F2). The kernel is entered, handles the page fault, and + // then returns to the called function. The problem is that + // this looks like a new function call to F2 from the kernel. + // The following code cleans up the stack by popping the + // kernel frames back to F1 (but not including F1). The + // return address for F2 also has to be fixed up to point to + // F1 instead of the kernel. + // + // We detect this case by checking if the previous basic block + // was in the kernel and the current basic block is not. + if ((mPrevFunction->region->flags & kIsKernelRegion) + && ((function->region->flags & kIsKernelRegion) == 0) + && mTop > 0) { + // We are switching from kernel mode to user mode. +#if 0 + printf(" doPush(): popping to user mode, bb_addr: 0x%08x\n", + event->bb_addr); + showStack(); +#endif + do { + // Pop off the kernel frames until we reach the one that + // caused the exception. + doSimplePop(time); + + // If the next stack frame is the one that caused an + // exception then stop popping frames. + if (mTop > 0 + && (mFrames[mTop - 1].flags & FRAME::kCausedException)) { + mFrames[mTop - 1].flags &= ~FRAME::kCausedException; + retAddr = mFrames[mTop].addr; + break; + } + } while (mTop > 0); +#if 0 + printf(" doPush() popping to level %d, using retAddr 0x%08x\n", + mTop, retAddr); +#endif + } + + // If we are starting an exception handler, then mark the previous + // stack frame so that we know where to return when the exception + // handler finishes. + if ((function->flags & symbol_type::kIsVectorStart) && mTop > 0) + mFrames[mTop - 1].flags |= FRAME::kCausedException; + + doSimplePush(function, retAddr, time); +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function, + uint32_t addr, uint64_t time) +{ + // Check for stack overflow + if (mTop >= mNumFrames) { + showStack(); + fprintf(stderr, "too many stack frames (%d)\n", mTop); + exit(1); + } + + // Keep track of the number of Java methods we push on the stack. + if (!mNativeOnly && function->vm_sym != NULL) { + // If we are pushing the first Java method on the stack, then + // save a snapshot of the stack so that we can clean things up + // later when we pop off the last Java stack frame. + if (mJavaTop == 0) { + transitionToJava(); + } + mJavaTop += 1; + } + + mFrames[mTop].addr = addr; + mFrames[mTop].function = function; + mFrames[mTop].flags = 0; + mFrames[mTop].time = time; + mFrames[mTop].global_time = time + mSkippedTime; + + // If the function being pushed is a Java method, then mark it on + // the stack so that we don't pop it off until we get a matching + // trace record from the method trace file. + if (function->vm_sym != NULL) { + mFrames[mTop].flags = FRAME::kInterpreted; + } + + mFrames[mTop].push(mTop, time, this); + mTop += 1; +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::doSimplePop(uint64_t time) +{ + if (mTop <= 0) { + return; + } + + mTop -= 1; + mFrames[mTop].pop(mTop, time, this); + + // Keep track of the number of Java methods we have on the stack. + symbol_type *function = mFrames[mTop].function; + if (!mNativeOnly && function->vm_sym != NULL) { + mJavaTop -= 1; + + // When there are no more Java stack frames, then clean up + // the client's stack. We need to do this because the client + // doesn't see the changes to the native stack underlying the + // fake Java stack until the last Java method is popped off. + if (mJavaTop == 0) { + transitionFromJava(time); + } + } +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::doPop(BBEvent *event, symbol_type *function, + Action methodAction) +{ + uint64_t time = event->time - mSkippedTime; + + // Search backward on the stack for a matching return address. + // The most common case is that we pop one stack frame, but + // sometimes we pop more than one. + int stackLevel; + bool allowMethodPop = (methodAction == POP); + for (stackLevel = mTop - 1; stackLevel >= 0; --stackLevel) { + if (event->bb_addr == mFrames[stackLevel].addr) { + // We found a matching return address on the stack. + break; + } + + // If this stack frame caused an exception, then do not pop + // this stack frame. + if (mFrames[stackLevel].flags & FRAME::kPopBarrier) { + // If this is a Java method, then allow a pop only if we + // have a matching trace record. + if (mFrames[stackLevel].flags & FRAME::kInterpreted) { + if (allowMethodPop) { + // Allow at most one method pop + allowMethodPop = false; + continue; + } + } + stackLevel += 1; + break; + } + } + + // If we didn't find a matching return address then search the stack + // again for a matching function. + if (stackLevel < 0 || event->bb_addr != mFrames[stackLevel].addr) { + bool allowMethodPop = (methodAction == POP); + for (stackLevel = mTop - 1; stackLevel >= 0; --stackLevel) { + // Compare the function with the one in the stack frame. + if (function == mFrames[stackLevel].function) { + // We found a matching function. We want to pop up to but not + // including this frame. + stackLevel += 1; + break; + } + + // If this stack frame caused an exception, then do not pop + // this stack frame. + if (mFrames[stackLevel].flags & FRAME::kPopBarrier) { + // If this is a Java method, then allow a pop only if we + // have a matching trace record. + if (mFrames[stackLevel].flags & FRAME::kInterpreted) { + if (allowMethodPop) { + // Allow at most one method pop + allowMethodPop = false; + continue; + } + } + stackLevel += 1; + break; + } + } + if (stackLevel < 0) + stackLevel = 0; + } + + // Note that if we didn't find a matching stack frame, we will pop + // the whole stack (unless there is a Java method or exception + // frame on the stack). This is intentional because we may have + // started the trace in the middle of an executing program that is + // returning up the stack and we do not know the whole stack. So + // the right thing to do is to empty the stack. + + // If we are emptying the stack, then add the current function + // on top. If the current function is the same as the top of + // stack, then avoid an extraneous pop and push. + if (stackLevel == 0 && mFrames[0].function == function) + stackLevel = 1; + +#if 0 + if (mTop - stackLevel > 7) { + printf("popping thru level %d\n", stackLevel); + showStack(); + } +#endif + + // Pop the stack frames + for (int ii = mTop - 1; ii >= stackLevel; --ii) + doSimplePop(time); + + // Clear the "caused exception" bit on the current stack frame + if (mTop > 0) { + mFrames[mTop - 1].flags &= ~FRAME::kCausedException; + } + + // Also handle the case where F1 calls F2 and F2 returns to + // F1, but before we can execute any instructions in F1, we + // switch to the kernel. Then when we return from the kernel + // we want to pop off F2 from the stack instead of pushing F1 + // on top of F2. To handle this case, we saved the last + // user-mode basic block when we entered the kernel (in + // the getAction() function) and now we can check to see if + // that was a return to F1 instead of a call. We use the + // getAction() function to determine this. + const uint32_t kIsKernelRegion = region_type::kIsKernelRegion; + if ((mPrevFunction->region->flags & kIsKernelRegion) + && ((function->region->flags & kIsKernelRegion) == 0)) { + mPrevEvent = mUserEvent; + mPrevFunction = mUserFunction; + if (getAction(event, function) == POP) { + // We may need to pop more than one frame, so just + // call doPop() again. This won't be an infinite loop + // here because we changed mPrevEvent to the last + // user-mode event. + doPop(event, function, methodAction); + return; + } + } +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::popAll(uint64_t time) +{ + time -= mSkippedTime; + while (mTop != 0) { + doSimplePop(time); + } +} + +template<class FRAME, class BASE> +typename CallStack<FRAME, BASE>::Action +CallStack<FRAME, BASE>::getMethodAction(BBEvent *event, symbol_type *function) +{ + if (function->vm_sym == NULL && mPrevFunction->vm_sym == NULL) { + return NONE; + } + + Action action = NONE; + uint32_t prevAddr = mPrevFunction->addr + mPrevFunction->region->base_addr; + uint32_t addr = function->addr + function->region->base_addr; + + // If the events get ahead of the method trace, then read ahead until we + // sync up again. This can happen if there is a pop of a method in the + // method trace for which we don't have a previous push. + while (event->time >= sNextMethod.time) { + sCurrentMethod = sNextMethod; + if (mTrace->ReadMethod(&sNextMethod)) { + sNextMethod.time = ~0ull; + } + } + + if (event->time >= sCurrentMethod.time) { + if (addr == sCurrentMethod.addr || prevAddr == sCurrentMethod.addr) { + action = (sCurrentMethod.flags == 0) ? PUSH : POP; + // We found a match, so read the next record. + sCurrentMethod = sNextMethod; + if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) { + sNextMethod.time = ~0ull; + } + } + } + return action; +} + +// When the first Java method is pushed on the stack, this method is +// called to save a snapshot of the current native stack so that the +// client's view of the native stack can be patched up later when the +// Java stack is empty. +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::transitionToJava() +{ + mSnapshotTop = mTop; + for (int ii = 0; ii < mTop; ++ii) { + mSnapshotFrames[ii] = mFrames[ii]; + } +} + +// When the Java stack becomes empty, the native stack becomes +// visible. This method is called when the Java stack becomes empty +// to patch up the client's view of the native stack, which may have +// changed underneath the Java stack. The stack snapshot is used to +// create a sequence of pops and pushes to make the client's view of +// the native stack match the current native stack. +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::transitionFromJava(uint64_t time) +{ + int top = mTop; + if (top > mSnapshotTop) { + top = mSnapshotTop; + } + for (int ii = 0; ii < top; ++ii) { + if (mSnapshotFrames[ii].function->addr == mFrames[ii].function->addr) { + continue; + } + + // Pop off all the rest of the frames from the snapshot + for (int jj = top - 1; jj >= ii; --jj) { + mSnapshotFrames[jj].pop(jj, time, this); + } + + // Push the new frames from the native stack + for (int jj = ii; jj < mTop; ++jj) { + mFrames[jj].push(jj, time, this); + } + break; + } +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::showStack() +{ + fprintf(stderr, "mTop: %d skippedTime: %llu\n", mTop, mSkippedTime); + for (int ii = 0; ii < mTop; ++ii) { + fprintf(stderr, " %d: t %d gt %d f %x 0x%08x 0x%08x %s\n", + ii, mFrames[ii].time, mFrames[ii].global_time, + mFrames[ii].flags, + mFrames[ii].addr, mFrames[ii].function->addr, + mFrames[ii].function->name); + } +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::showSnapshotStack() +{ + fprintf(stderr, "mSnapshotTop: %d\n", mSnapshotTop); + for (int ii = 0; ii < mSnapshotTop; ++ii) { + fprintf(stderr, " %d: t %d f %x 0x%08x 0x%08x %s\n", + ii, mSnapshotFrames[ii].time, mSnapshotFrames[ii].flags, + mSnapshotFrames[ii].addr, mSnapshotFrames[ii].function->addr, + mSnapshotFrames[ii].function->name); + } +} + +#endif /* CALL_STACK_H */ diff --git a/emulator/qtools/check_trace.cpp b/emulator/qtools/check_trace.cpp new file mode 100644 index 0000000..d933a87 --- /dev/null +++ b/emulator/qtools/check_trace.cpp @@ -0,0 +1,61 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "armdis.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +static const uint32_t kOffsetThreshold = 0x100000; + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file\n", program); + OptionsUsage(); +} + +int main(int argc, char **argv) { + // Parse the options + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<> *trace = new TraceReader<>; + trace->Open(trace_filename); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + while (1) { + symbol_type *sym; + BBEvent event; + BBEvent ignored; + + if (GetNextValidEvent(trace, &event, &ignored, &sym)) + break; + if (event.bb_num == 0) + break; + //printf("t%llu bb %lld %d\n", event.time, event.bb_num, event.num_insns); + uint64_t insn_time = trace->ReadInsnTime(event.time); + if (insn_time != event.time) { + printf("time: %llu insn time: %llu bb: %llu addr: 0x%x num_insns: %d, pid: %d\n", + event.time, insn_time, event.bb_num, event.bb_addr, + event.num_insns, event.pid); + exit(1); + } + for (int ii = 1; ii < event.num_insns; ++ii) { + trace->ReadInsnTime(event.time); + } + } + + delete trace; + return 0; +} diff --git a/emulator/qtools/coverage.cpp b/emulator/qtools/coverage.cpp new file mode 100644 index 0000000..fb1fe52 --- /dev/null +++ b/emulator/qtools/coverage.cpp @@ -0,0 +1,153 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "trace_reader.h" +#include "parse_options.h" +#include "opcode.h" + +const int kMillion = 1000000; +const int kMHz = 200 * kMillion; + +struct symbol { + int numCalls; // number of times this function is called +}; + +typedef TraceReader<symbol> TraceReaderType; + +#include "parse_options-inl.h" +#include "callstack.h" + +class MyFrame : public StackFrame<symbol_type> { + public: + void push(int stackLevel, uint64_t time, CallStackBase *base) { + function->numCalls += 1; + } + void pop(int stackLevel, uint64_t time, CallStackBase *base) { + } +}; + +typedef CallStack<MyFrame> CallStackType; + +static const int kNumStackFrames = 500; +static const int kMaxThreads = (32 * 1024); +CallStackType *stacks[kMaxThreads]; + +// This comparison function is called from qsort() to sort symbols +// into decreasing number of calls. +int cmp_sym_calls(const void *a, const void *b) { + const symbol_type *syma, *symb; + uint64_t calls1, calls2; + + syma = static_cast<symbol_type const *>(a); + symb = static_cast<symbol_type const *>(b); + calls1 = syma->numCalls; + calls2 = symb->numCalls; + if (calls1 < calls2) + return 1; + if (calls1 == calls2) { + int cmp = strcmp(syma->name, symb->name); + if (cmp == 0) + cmp = strcmp(syma->region->path, symb->region->path); + return cmp; + } + return -1; +} + +// This comparison function is called from qsort() to sort symbols +// into alphabetical order. +int cmp_sym_names(const void *a, const void *b) { + const symbol_type *syma, *symb; + + syma = static_cast<symbol_type const *>(a); + symb = static_cast<symbol_type const *>(b); + int cmp = strcmp(syma->region->path, symb->region->path); + if (cmp == 0) + cmp = strcmp(syma->name, symb->name); + return cmp; +} + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file\n", program); + OptionsUsage(); +} + +int main(int argc, char **argv) +{ + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<symbol> *trace = new TraceReader<symbol>; + trace->Open(trace_filename); + trace->SetDemangle(demangle); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + BBEvent event; + while (1) { + BBEvent ignored; + symbol_type *function; + + if (GetNextValidEvent(trace, &event, &ignored, &function)) + break; + if (event.bb_num == 0) + break; + + // Get the stack for the current thread + CallStackType *pStack = stacks[event.pid]; + + // If the stack does not exist, then allocate a new one. + if (pStack == NULL) { + pStack = new CallStackType(event.pid, kNumStackFrames, trace); + stacks[event.pid] = pStack; + } + + // Update the stack + pStack->updateStack(&event, function); + } + + for (int ii = 0; ii < kMaxThreads; ++ii) { + if (stacks[ii]) + stacks[ii]->popAll(event.time); + } + + int nsyms; + symbol_type *syms = trace->GetSymbols(&nsyms); + + // Sort the symbols into decreasing number of calls + qsort(syms, nsyms, sizeof(symbol_type), cmp_sym_names); + + symbol_type *psym = syms; + for (int ii = 0; ii < nsyms; ++ii, ++psym) { + // Ignore functions with non-zero calls + if (psym->numCalls) + continue; + + // Ignore some symbols + if (strcmp(psym->name, "(end)") == 0) + continue; + if (strcmp(psym->name, "(unknown)") == 0) + continue; + if (strcmp(psym->name, ".plt") == 0) + continue; + char *ksym = " "; + if (psym->region->flags & region_type::kIsKernelRegion) + ksym = "k"; + printf("%s %s %s\n", ksym, psym->name, psym->region->path); +#if 0 + printf("#%d %5d %s %s %s\n", ii + 1, psym->numCalls, ksym, psym->name, + psym->region->path); +#endif + } + delete[] syms; + delete trace; + + return 0; +} diff --git a/emulator/qtools/decoder.cpp b/emulator/qtools/decoder.cpp new file mode 100644 index 0000000..ec53181 --- /dev/null +++ b/emulator/qtools/decoder.cpp @@ -0,0 +1,278 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "decoder.h" +#include "trace_common.h" + +// This array provides a fast conversion from the initial byte in +// a varint-encoded object to the length (in bytes) of that object. +int prefix_to_len[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 9, 9, 17, 17 +}; + +// This array provides a fast conversion from the initial byte in +// a varint-encoded object to the initial data bits for that object. +int prefix_to_data[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 0, 0, 0, 0 +}; + +signed char prefix_to_signed_data[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0xfe, 0xff, 0x00, 0xff, 0x00, 0xff, +}; + +Decoder::Decoder() +{ + filename_ = NULL; + fstream_ = NULL; + next_ = NULL; + end_ = NULL; +} + +Decoder::~Decoder() +{ + Close(); + delete[] filename_; +} + +void Decoder::Close() +{ + if (fstream_) { + fclose(fstream_); + fstream_ = NULL; + } +} + +void Decoder::Open(char *filename) +{ + if (filename_ != NULL) { + delete[] filename_; + } + filename_ = new char[strlen(filename) + 1]; + strcpy(filename_, filename); + fstream_ = fopen(filename_, "r"); + if (fstream_ == NULL) { + perror(filename_); + exit(1); + } + + int rval = fread(buf_, 1, kBufSize, fstream_); + if (rval != kBufSize) { + if (ferror(fstream_)) { + perror(filename_); + exit(1); + } + if (!feof(fstream_)) { + fprintf(stderr, "Unexpected short fread() before eof\n"); + exit(1); + } + } + next_ = buf_; + end_ = buf_ + rval; +} + +void Decoder::FillBuffer() +{ + assert(next_ <= end_); + + if (end_ - next_ < kDecodingSpace && end_ == &buf_[kBufSize]) { + // Copy the unused bytes left at the end to the beginning of the + // buffer. + int len = end_ - next_; + if (len > 0) + memcpy(buf_, next_, len); + + // Read enough bytes to fill up the buffer, if possible. + int nbytes = kBufSize - len; + int rval = fread(buf_ + len, 1, nbytes, fstream_); + if (rval < nbytes) { + if (ferror(fstream_)) { + perror(filename_); + exit(1); + } + if (!feof(fstream_)) { + fprintf(stderr, "Unexpected short fread() before eof\n"); + exit(1); + } + } + end_ = &buf_[len + rval]; + next_ = buf_; + } +} + +void Decoder::Read(char *dest, int len) +{ + while (len > 0) { + int nbytes = end_ - next_; + if (nbytes == 0) { + FillBuffer(); + nbytes = end_ - next_; + if (nbytes == 0) + break; + } + if (nbytes > len) + nbytes = len; + memcpy(dest, next_, nbytes); + dest += nbytes; + len -= nbytes; + next_ += nbytes; + } +} + +// Decode a varint-encoded object starting at the current position in +// the array "buf_" and return the decoded value as a 64-bit integer. +// A varint-encoded object has an initial prefix that specifies how many +// data bits follow. If the first bit is zero, for example, then there +// are 7 data bits that follow. The table below shows the prefix values +// and corresponding data bits. +// +// Prefix Bytes Data bits +// 0 1 7 +// 10 2 14 +// 110 3 21 +// 1110 4 28 +// 11110 5 35 +// 111110 6 42 +// 11111100 9 64 +// 11111101 reserved +// 11111110 reserved +// 11111111 reserved +int64_t Decoder::Decode(bool is_signed) +{ + int64_t val64; + + if (end_ - next_ < kDecodingSpace) + FillBuffer(); + +#if BYTE_ORDER == BIG_ENDIAN + uint8_t byte0 = *next_; + + // Get the number of bytes to decode based on the first byte. + int len = prefix_to_len[byte0]; + + if (next_ + len > end_) { + fprintf(stderr, "%s: decoding past end of file.\n", filename_); + exit(1); + } + + // Get the first data byte. + if (is_signed) + val64 = prefix_to_signed_data[byte0]; + else + val64 = prefix_to_data[byte0]; + + next_ += 1; + for (int ii = 1; ii < len; ++ii) { + val64 = (val64 << 8) | *next_++; + } +#else + // If we are on a little-endian machine, then use large, unaligned loads. + uint64_t data = *(reinterpret_cast<uint64_t*>(next_)); + uint8_t byte0 = data; + data = bswap64(data); + + // Get the number of bytes to decode based on the first byte. + int len = prefix_to_len[byte0]; + + if (next_ + len > end_) { + fprintf(stderr, "%s: decoding past end of file.\n", filename_); + exit(1); + } + + // Get the first data byte. + if (is_signed) + val64 = prefix_to_signed_data[byte0]; + else + val64 = prefix_to_data[byte0]; + + switch (len) { + case 1: + break; + case 2: + val64 = (val64 << 8) | ((data >> 48) & 0xffull); + break; + case 3: + val64 = (val64 << 16) | ((data >> 40) & 0xffffull); + break; + case 4: + val64 = (val64 << 24) | ((data >> 32) & 0xffffffull); + break; + case 5: + val64 = (val64 << 32) | ((data >> 24) & 0xffffffffull); + break; + case 6: + val64 = (val64 << 40) | ((data >> 16) & 0xffffffffffull); + break; + case 9: + data = *(reinterpret_cast<uint64_t*>(&next_[1])); + val64 = bswap64(data); + break; + } + next_ += len; +#endif + return val64; +} diff --git a/emulator/qtools/decoder.h b/emulator/qtools/decoder.h new file mode 100644 index 0000000..44905fd --- /dev/null +++ b/emulator/qtools/decoder.h @@ -0,0 +1,28 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <inttypes.h> + +class Decoder { + public: + Decoder(); + ~Decoder(); + + void Open(char *filename); + void Close(); + int64_t Decode(bool is_signed); + void Read(char *dest, int len); + bool IsEOF() { return (end_ == next_) && feof(fstream_); } + + private: + static const int kBufSize = 4096; + static const int kDecodingSpace = 9; + + void FillBuffer(); + + char *filename_; + FILE *fstream_; + uint8_t buf_[kBufSize]; + uint8_t *next_; + uint8_t *end_; +}; diff --git a/emulator/qtools/dmtrace.cpp b/emulator/qtools/dmtrace.cpp new file mode 100644 index 0000000..6d9250a --- /dev/null +++ b/emulator/qtools/dmtrace.cpp @@ -0,0 +1,254 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <inttypes.h> +#include <string.h> +#include "dmtrace.h" + +static const short kVersion = 2; + +const DmTrace::Header DmTrace::header = { + 0x574f4c53, kVersion, sizeof(DmTrace::Header), 0LL +}; + +static char *keyHeader = "*version\n" "2\n" "clock=thread-cpu\n"; +static char *keyThreadHeader = "*threads\n"; +static char *keyFunctionHeader = "*methods\n"; +static char *keyEnd = "*end\n"; + +DmTrace::DmTrace() { + fData = NULL; + fTrace = NULL; + threads = new std::vector<ThreadRecord*>; + functions = new std::vector<FunctionRecord*>; +} + +DmTrace::~DmTrace() { + delete threads; + delete functions; +} + +void DmTrace::open(const char *dmtrace_file, uint64_t start_time) +{ + fTrace = fopen(dmtrace_file, "w"); + if (fTrace == NULL) { + perror(dmtrace_file); + exit(1); + } + + // Make a temporary file to write the data into. + char tmpData[32]; + strcpy(tmpData, "/tmp/dmtrace-data-XXXXXX"); + int data_fd = mkstemp(tmpData); + if (data_fd < 0) { + perror("Cannot create temporary file"); + exit(1); + } + + // Ensure it goes away on exit. + unlink(tmpData); + fData = fdopen(data_fd, "w+"); + if (fData == NULL) { + perror("Can't make temp data file"); + exit(1); + } + + writeHeader(fData, start_time); +} + +void DmTrace::close() +{ + if (fTrace == NULL) + return; + writeKeyFile(fTrace); + + // Take down how much data we wrote to the temp data file. + long size = ftell(fData); + // Rewind the data file and append its contents to the trace file. + rewind(fData); + char *data = (char *)malloc(size); + fread(data, size, 1, fData); + fwrite(data, size, 1, fTrace); + free(data); + fclose(fData); + fclose(fTrace); +} + +/* + * Write values to the binary data file. + */ +void DmTrace::write2LE(FILE* fstream, unsigned short val) +{ + putc(val & 0xff, fstream); + putc(val >> 8, fstream); +} + +void DmTrace::write4LE(FILE* fstream, unsigned int val) +{ + putc(val & 0xff, fstream); + putc((val >> 8) & 0xff, fstream); + putc((val >> 16) & 0xff, fstream); + putc((val >> 24) & 0xff, fstream); +} + +void DmTrace::write8LE(FILE* fstream, unsigned long long val) +{ + putc(val & 0xff, fstream); + putc((val >> 8) & 0xff, fstream); + putc((val >> 16) & 0xff, fstream); + putc((val >> 24) & 0xff, fstream); + putc((val >> 32) & 0xff, fstream); + putc((val >> 40) & 0xff, fstream); + putc((val >> 48) & 0xff, fstream); + putc((val >> 56) & 0xff, fstream); +} + +void DmTrace::writeHeader(FILE *fstream, uint64_t startTime) +{ + write4LE(fstream, header.magic); + write2LE(fstream, header.version); + write2LE(fstream, header.offset); + write8LE(fstream, startTime); +} + +void DmTrace::writeDataRecord(FILE *fstream, int threadId, + unsigned int methodVal, + unsigned int elapsedTime) +{ + write2LE(fstream, threadId); + write4LE(fstream, methodVal); + write4LE(fstream, elapsedTime); +} + +void DmTrace::addFunctionEntry(int functionId, uint32_t cycle, uint32_t pid) +{ + writeDataRecord(fData, pid, functionId, cycle); +} + +void DmTrace::addFunctionExit(int functionId, uint32_t cycle, uint32_t pid) +{ + writeDataRecord(fData, pid, functionId | 1, cycle); +} + +void DmTrace::addFunction(int functionId, const char *name) +{ + FunctionRecord *rec = new FunctionRecord; + rec->id = functionId; + rec->name = name; + functions->push_back(rec); +} + +void DmTrace::addFunction(int functionId, const char *clazz, + const char *method, const char *sig) +{ + // Allocate space for all the strings, plus 2 tab separators plus null byte. + // We currently don't reclaim this space. + int len = strlen(clazz) + strlen(method) + strlen(sig) + 3; + char *name = new char[len]; + sprintf(name, "%s\t%s\t%s", clazz, method, sig); + + addFunction(functionId, name); +} + +void DmTrace::parseAndAddFunction(int functionId, const char *name) +{ + // Parse the "name" string into "class", "method" and "signature". + // The "name" string should look something like this: + // name = "java.util.LinkedList.size()I" + // and it will be parsed into this: + // clazz = "java.util.LinkedList" + // method = "size" + // sig = "()I" + + // Find the first parenthesis, the start of the signature. + char *paren = strchr(name, '('); + + // If not found, then add the original name. + if (paren == NULL) { + addFunction(functionId, name); + return; + } + + // Copy the signature + int len = strlen(paren) + 1; + char *sig = new char[len]; + strcpy(sig, paren); + + // Zero the parenthesis so that we can search backwards from the signature + *paren = 0; + + // Search for the last period, the start of the method name + char *dot = strrchr(name, '.'); + + // If not found, then add the original name. + if (dot == NULL || dot == name) { + delete[] sig; + *paren = '('; + addFunction(functionId, name); + return; + } + + // Copy the method, not including the dot + len = strlen(dot + 1) + 1; + char *method = new char[len]; + strcpy(method, dot + 1); + + // Zero the dot to delimit the class name + *dot = 0; + + addFunction(functionId, name, method, sig); + + // Free the space we allocated. + delete[] sig; + delete[] method; +} + +void DmTrace::addThread(int threadId, const char *name) +{ + ThreadRecord *rec = new ThreadRecord; + rec->id = threadId; + rec->name = name; + threads->push_back(rec); +} + +void DmTrace::updateName(int threadId, const char *name) +{ + std::vector<ThreadRecord*>::iterator iter; + + for (iter = threads->begin(); iter != threads->end(); ++iter) { + if ((*iter)->id == threadId) { + (*iter)->name = name; + return; + } + } +} + +void DmTrace::writeKeyFile(FILE *fstream) +{ + fwrite(keyHeader, strlen(keyHeader), 1, fstream); + writeThreads(fstream); + writeFunctions(fstream); + fwrite(keyEnd, strlen(keyEnd), 1, fstream); +} + +void DmTrace::writeThreads(FILE *fstream) +{ + std::vector<ThreadRecord*>::iterator iter; + + fwrite(keyThreadHeader, strlen(keyThreadHeader), 1, fstream); + for (iter = threads->begin(); iter != threads->end(); ++iter) { + fprintf(fstream, "%d\t%s\n", (*iter)->id, (*iter)->name); + } +} + +void DmTrace::writeFunctions(FILE *fstream) +{ + std::vector<FunctionRecord*>::iterator iter; + + fwrite(keyFunctionHeader, strlen(keyFunctionHeader), 1, fstream); + for (iter = functions->begin(); iter != functions->end(); ++iter) { + fprintf(fstream, "0x%x\t%s\n", (*iter)->id, (*iter)->name); + } +} diff --git a/emulator/qtools/dmtrace.h b/emulator/qtools/dmtrace.h new file mode 100644 index 0000000..6e20921 --- /dev/null +++ b/emulator/qtools/dmtrace.h @@ -0,0 +1,61 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef DMTRACE_H +#define DMTRACE_H + +#include <vector> + +class DmTrace { + public: + struct Header { + uint32_t magic; + uint16_t version; + uint16_t offset; + uint64_t date_time; + }; + + DmTrace(); + ~DmTrace(); + + void open(const char *dmtrace_file, uint64_t startTime); + void close(); + void addFunctionEntry(int methodId, uint32_t cycle, uint32_t pid); + void addFunctionExit(int methodId, uint32_t cycle, uint32_t pid); + void addFunction(int functionId, const char *name); + void addFunction(int functionId, const char *clazz, const char *method, + const char *sig); + void parseAndAddFunction(int functionId, const char *name); + void addThread(int threadId, const char *name); + void updateName(int threadId, const char *name); + + private: + static const Header header; + + struct ThreadRecord { + int id; + const char *name; + }; + + struct FunctionRecord { + int id; + const char *name; + }; + + void write2LE(FILE* fstream, unsigned short val); + void write4LE(FILE* fstream, unsigned int val); + void write8LE(FILE* fstream, unsigned long long val); + void writeHeader(FILE *fstream, uint64_t startTime); + void writeDataRecord(FILE *fstream, int threadId, + unsigned int methodVal, + unsigned int elapsedTime); + void writeKeyFile(FILE *fstream); + void writeThreads(FILE *fstream); + void writeFunctions(FILE *fstream); + + FILE *fData; + FILE *fTrace; + std::vector<ThreadRecord*> *threads; + std::vector<FunctionRecord*> *functions; +}; + +#endif // DMTRACE_H diff --git a/emulator/qtools/exc_dump.cpp b/emulator/qtools/exc_dump.cpp new file mode 100644 index 0000000..166586f --- /dev/null +++ b/emulator/qtools/exc_dump.cpp @@ -0,0 +1,28 @@ +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include "trace_reader_base.h" + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->Open(trace_filename); + + while (1) { + uint64_t time, recnum, bb_num, bb_start_time; + uint32_t pc, target_pc; + int num_insns; + + if (trace->ReadExc(&time, &pc, &recnum, &target_pc, &bb_num, + &bb_start_time, &num_insns)) + break; + printf("time: %lld rec: %llu pc: %08x target: %08x bb: %llu bb_start: %llu insns: %d\n", + time, recnum, pc, target_pc, bb_num, bb_start_time, num_insns); + } + return 0; +} diff --git a/emulator/qtools/gtrace.cpp b/emulator/qtools/gtrace.cpp new file mode 100644 index 0000000..673d8a4 --- /dev/null +++ b/emulator/qtools/gtrace.cpp @@ -0,0 +1,152 @@ +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include "gtrace.h" + +// A buffer of zeros +static char zeros[Gtrace::kGtraceEntriesPerBlock * sizeof(Gtrace::trace_entry)]; + +Gtrace::Gtrace() { + gtrace_file_ = NULL; + ftrace_ = NULL; + fnames_ = NULL; + start_sec_ = 0; + pdate_ = 0; + ptime_ = 0; + num_entries_ = 0; + blockno_ = 1; + current_pid_ = 0; +} + +Gtrace::~Gtrace() { + if (ftrace_) { + // Extend the trace file to a multiple of 8k. Otherwise gtracepost64 + // complains. + long pos = ftell(ftrace_); + long pos_end = (pos + 0x1fff) & ~0x1fff; + if (pos_end > pos) { + char ch = 0; + fseek(ftrace_, pos_end - 1, SEEK_SET); + fwrite(&ch, 1, 1, ftrace_); + } + fclose(ftrace_); + } + if (fnames_) + fclose(fnames_); +} + +void Gtrace::Open(const char *gtrace_file, uint32_t pdate, uint32_t ptime) +{ + ftrace_ = fopen(gtrace_file, "w"); + if (ftrace_ == NULL) { + perror(gtrace_file); + exit(1); + } + gtrace_file_ = gtrace_file; + + pdate_ = pdate; + ptime_ = ptime; + sprintf(gname_file_, "gname_%x_%06x.txt", pdate, ptime); + fnames_ = fopen(gname_file_, "w"); + if (fnames_ == NULL) { + perror(gname_file_); + exit(1); + } + fprintf(fnames_, "# File# Proc# Line# Name\n"); +} + +void Gtrace::WriteFirstHeader(uint32_t start_sec, uint32_t pid) +{ + first_header fh; + current_pid_ = pid; + start_sec_ = start_sec; + FillFirstHeader(start_sec, pid, &fh); + fwrite(&fh, sizeof(fh), 1, ftrace_); + num_entries_ = 8; +} + +void Gtrace::FillFirstHeader(uint32_t start_sec, uint32_t pid, + first_header *fh) { + int cpu = 0; + int max_files = 16; + int max_procedures = 12; + + fh->common.blockno = 0; + fh->common.entry_width = 8; + fh->common.block_tic = kBaseTic; + fh->common.block_time = start_sec; + //fh->common.usec_cpu = (start_usec << 8) | (cpu & 0xff); + fh->common.usec_cpu = cpu & 0xff; + fh->common.pid = pid; + fh->common.bug_count = 0; + fh->common.zero_count = 0; + + fh->tic = kBaseTic + 1; + fh->one = 1; + fh->tics_per_second = kTicsPerSecond; + fh->trace_time = start_sec; + fh->version = 5; + fh->file_proc = (max_files << 8) | max_procedures; + fh->pdate = pdate_; + fh->ptime = ptime_; +} + +void Gtrace::WriteBlockHeader(uint32_t cycle, uint32_t pid) +{ + int cpu = 0; + block_header bh; + + bh.blockno = blockno_++; + bh.entry_width = 8; + bh.block_tic = cycle + kBaseTic; + bh.block_time = start_sec_ + cycle / kTicsPerSecond; + //bh.usec_cpu = (start_usec << 8) | (cpu & 0xff); + bh.usec_cpu = cpu & 0xff; + bh.pid = pid; + bh.bug_count = 0; + bh.zero_count = 0; + fwrite(&bh, sizeof(bh), 1, ftrace_); +} + +void Gtrace::AddGtraceRecord(int filenum, int procnum, uint32_t cycle, uint32_t pid, + int is_exit) +{ + trace_entry entry; + + if (current_pid_ != pid) { + current_pid_ = pid; + + // We are switching to a new process id, so pad the current block + // with zeros. + int num_zeros = (kGtraceEntriesPerBlock - num_entries_) * sizeof(entry); + fwrite(zeros, num_zeros, 1, ftrace_); + WriteBlockHeader(cycle, pid); + num_entries_ = 4; + } + + // If the current block is full, write out a new block header + if (num_entries_ == kGtraceEntriesPerBlock) { + WriteBlockHeader(cycle, pid); + num_entries_ = 4; + } + + entry.cycle = cycle + kBaseTic; + entry.event = (filenum << 13) | (procnum << 1) | is_exit; + fwrite(&entry, sizeof(entry), 1, ftrace_); + num_entries_ += 1; +} + +void Gtrace::AddProcEntry(int filenum, int procnum, uint32_t cycle, uint32_t pid) +{ + AddGtraceRecord(filenum, procnum, cycle, pid, 0); +} + +void Gtrace::AddProcExit(int filenum, int procnum, uint32_t cycle, uint32_t pid) +{ + AddGtraceRecord(filenum, procnum, cycle, pid, 1); +} + +void Gtrace::AddProcedure(int filenum, int procnum, const char *proc_name) +{ + fprintf(fnames_, "%d %d %d %s\n", filenum, procnum, procnum, proc_name); +} diff --git a/emulator/qtools/gtrace.h b/emulator/qtools/gtrace.h new file mode 100644 index 0000000..542adc2 --- /dev/null +++ b/emulator/qtools/gtrace.h @@ -0,0 +1,69 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef GTRACE_H +#define GTRACE_H + +class Gtrace { + public: + static const int kGtraceEntriesPerBlock = 1024; + static const uint32_t kMillion = 1000000; + static const uint32_t kTicsPerSecond = 200 * kMillion; + static const int kBaseTic = 0x1000; + + struct trace_entry { + uint32_t cycle; + uint32_t event; + }; + + struct block_header { + uint32_t blockno; + uint32_t entry_width; + uint32_t block_tic; + uint32_t block_time; + uint32_t usec_cpu; + uint32_t pid; + uint32_t bug_count; + uint32_t zero_count; + }; + + struct first_header { + block_header common; + uint32_t tic; + uint32_t one; + uint32_t tics_per_second; + uint32_t trace_time; + uint32_t version; + uint32_t file_proc; + uint32_t pdate; + uint32_t ptime; + }; + + Gtrace(); + ~Gtrace(); + + void Open(const char *gtrace_file, uint32_t pdate, uint32_t ptime); + void WriteFirstHeader(uint32_t start_sec, uint32_t pid); + void AddProcedure(int filenum, int procnum, const char *proc_name); + void AddProcEntry(int filenum, int procnum, uint32_t cycle, uint32_t pid); + void AddProcExit(int filenum, int procnum, uint32_t cycle, uint32_t pid); + + private: + void AddGtraceRecord(int filenum, int procnum, uint32_t cycle, uint32_t pid, + int is_exit); + void FillFirstHeader(uint32_t start_sec, uint32_t pid, + first_header *fh); + void WriteBlockHeader(uint32_t cycle, uint32_t pid); + + const char *gtrace_file_; + char gname_file_[100]; + FILE *ftrace_; + FILE *fnames_; + uint32_t start_sec_; + uint32_t pdate_; + uint32_t ptime_; + int num_entries_; + int blockno_; + uint32_t current_pid_; +}; + +#endif // GTRACE_H diff --git a/emulator/qtools/hash_table.h b/emulator/qtools/hash_table.h new file mode 100644 index 0000000..45786ec --- /dev/null +++ b/emulator/qtools/hash_table.h @@ -0,0 +1,193 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef HASH_TABLE_H +#define HASH_TABLE_H + +#include <string.h> +#include <inttypes.h> + +template<class T> +class HashTable { + public: + HashTable(int size, T default_value = T()); + ~HashTable(); + + typedef struct entry { + entry *next; + char *key; + T value; + } entry_type; + + typedef T value_type; + + void Update(const char *key, T value); + T Find(const char *key); + entry_type* GetFirst(); + entry_type* GetNext(); + + private: + uint32_t HashFunction(const char *key); + + int size_; + int mask_; + T default_value_; + entry_type **table_; + int num_entries_; + int current_index_; + entry_type *current_ptr_; +}; + +template<class T> +HashTable<T>::HashTable(int size, T default_value) +{ + int pow2; + + // Round up size to a power of two + for (pow2 = 2; pow2 < size; pow2 <<= 1) + ; // empty body + + size_ = pow2; + mask_ = pow2 - 1; + default_value_ = default_value; + + // Allocate a table of pointers and initialize them all to NULL. + table_ = new entry_type*[size_]; + for (int ii = 0; ii < size_; ++ii) + table_[ii] = NULL; + num_entries_ = 0; + current_index_ = 0; + current_ptr_ = NULL; +} + +template<class T> +HashTable<T>::~HashTable() +{ + for (int ii = 0; ii < size_; ++ii) { + entry_type *ptr, *next; + + // Delete all the pointers in the chain at this table position. + // Save the next pointer before deleting each entry so that we + // don't dereference part of a deallocated object. + for (ptr = table_[ii]; ptr; ptr = next) { + next = ptr->next; + delete[] ptr->key; + delete ptr; + } + } + delete[] table_; +} + +// Professor Daniel J. Bernstein's hash function. See +// http://www.partow.net/programming/hashfunctions/ +template<class T> +uint32_t HashTable<T>::HashFunction(const char *key) +{ + uint32_t hash = 5381; + + int len = strlen(key); + for(int ii = 0; ii < len; ++key, ++ii) + hash = ((hash << 5) + hash) + *key; + + return hash; +} + +template<class T> +void HashTable<T>::Update(const char *key, T value) +{ + // Hash the key to get the table position + int len = strlen(key); + int pos = HashFunction(key) & mask_; + + // Search the chain for a matching key + for (entry_type *ptr = table_[pos]; ptr; ptr = ptr->next) { + if (strcmp(ptr->key, key) == 0) { + ptr->value = value; + return; + } + } + + // Create a new hash entry and fill in the values + entry_type *ptr = new entry_type; + + // Copy the string + ptr->key = new char[len + 1]; + strcpy(ptr->key, key); + ptr->value = value; + + // Insert the new entry at the beginning of the list + ptr->next = table_[pos]; + table_[pos] = ptr; + num_entries_ += 1; +} + +template<class T> +typename HashTable<T>::value_type HashTable<T>::Find(const char *key) +{ + // Hash the key to get the table position + int pos = HashFunction(key) & mask_; + + // Search the chain for a matching key + for (entry_type *ptr = table_[pos]; ptr; ptr = ptr->next) { + if (strcmp(ptr->key, key) == 0) + return ptr->value; + } + + // If we get here, then we didn't find the key + return default_value_; +} + +template<class T> +typename HashTable<T>::entry_type* HashTable<T>::GetFirst() +{ + // Find the first non-NULL table entry. + for (current_index_ = 0; current_index_ < size_; ++current_index_) { + if (table_[current_index_]) + break; + } + + // If there are no table entries, then return NULL. + if (current_index_ == size_) + return NULL; + + // Remember and return the current element. + current_ptr_ = table_[current_index_]; + return current_ptr_; +} + +template<class T> +typename HashTable<T>::entry_type* HashTable<T>::GetNext() +{ + // If we already iterated part way through the hash table, then continue + // to the next element. + if (current_ptr_) { + current_ptr_ = current_ptr_->next; + + // If we are pointing to a valid element, then return it. + if (current_ptr_) + return current_ptr_; + + // Otherwise, start searching at the next table index. + current_index_ += 1; + } + + // Find the next non-NULL table entry. + for (; current_index_ < size_; ++current_index_) { + if (table_[current_index_]) + break; + } + + // If there are no more non-NULL table entries, then return NULL. + if (current_index_ == size_) { + // Reset the current index so that we will start over from the + // beginning on the next call to GetNext(). + current_index_ = 0; + return NULL; + } + + // Remember and return the current element. + current_ptr_ = table_[current_index_]; + return current_ptr_; +} + + +#endif // HASH_TABLE_H diff --git a/emulator/qtools/hist_trace.cpp b/emulator/qtools/hist_trace.cpp new file mode 100644 index 0000000..d2c6a90 --- /dev/null +++ b/emulator/qtools/hist_trace.cpp @@ -0,0 +1,64 @@ +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include "trace_reader.h" + +static const int kMaxHistEntries = 256; +static const int kMaxHistEntries2 = kMaxHistEntries / 2; +int hist[kMaxHistEntries]; +int underflow, overflow; + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->Open(trace_filename); + + uint64_t prev_bb_num = 0; + uint64_t prev_time = 0; + int total = 0; + + while (1) { + BBEvent event; + + if (trace->ReadBB(&event)) + break; + int bb_diff = event.bb_num - prev_bb_num; + //int time_diff = event.time - prev_time; + //printf("bb_num: %llu prev: %llu, diff: %d\n", + // event.bb_num, prev_bb_num, bb_diff); + prev_bb_num = event.bb_num; + prev_time = event.time; + + bb_diff += kMaxHistEntries2; + if (bb_diff < 0) + underflow += 1; + else if (bb_diff >= kMaxHistEntries) + overflow += 1; + else + hist[bb_diff] += 1; + total += 1; + } + + int sum = 0; + double sum_per = 0; + double per = 0; + for (int ii = 0; ii < kMaxHistEntries; ++ii) { + if (hist[ii] == 0) + continue; + per = 100.0 * hist[ii] / total; + sum += hist[ii]; + sum_per = 100.0 * sum / total; + printf(" %4d: %6d %6.2f %6.2f\n", ii - kMaxHistEntries2, hist[ii], per, sum_per); + } + per = 100.0 * underflow / total; + printf("under: %6d %6.2f\n", underflow, per); + per = 100.0 * overflow / total; + printf("over: %6d %6.2f\n", overflow, per); + printf("total: %6d\n", total); + return 0; +} diff --git a/emulator/qtools/opcode.cpp b/emulator/qtools/opcode.cpp new file mode 100644 index 0000000..41bef3a --- /dev/null +++ b/emulator/qtools/opcode.cpp @@ -0,0 +1,204 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <inttypes.h> +#include "opcode.h" + +// Note: this array depends on the Opcode enum defined in opcode.h +uint32_t opcode_flags[] = { + 0, // OP_INVALID + 0, // OP_UNDEFINED + kCatAlu, // OP_ADC + kCatAlu, // OP_ADD + kCatAlu, // OP_AND + kCatBranch, // OP_B + kCatBranch | kCatBranchLink, // OP_BL + kCatAlu, // OP_BIC + 0, // OP_BKPT + kCatBranch | kCatBranchLink | kCatBranchExch, // OP_BLX + kCatBranch | kCatBranchExch, // OP_BX + kCatCoproc, // OP_CDP + kCatAlu, // OP_CLZ + kCatAlu, // OP_CMN + kCatAlu, // OP_CMP + kCatAlu, // OP_EOR + kCatCoproc | kCatLoad, // OP_LDC + kCatLoad | kCatMultiple, // OP_LDM + kCatLoad | kCatWord, // OP_LDR + kCatLoad | kCatByte, // OP_LDRB + kCatLoad | kCatByte, // OP_LDRBT + kCatLoad | kCatHalf, // OP_LDRH + kCatLoad | kCatByte | kCatSigned, // OP_LDRSB + kCatLoad | kCatHalf | kCatSigned, // OP_LDRSH + kCatLoad | kCatWord, // OP_LDRT + kCatCoproc, // OP_MCR + kCatAlu, // OP_MLA + kCatAlu, // OP_MOV + kCatCoproc, // OP_MRC + 0, // OP_MRS + 0, // OP_MSR + kCatAlu, // OP_MUL + kCatAlu, // OP_MVN + kCatAlu, // OP_ORR + 0, // OP_PLD + kCatAlu, // OP_RSB + kCatAlu, // OP_RSC + kCatAlu, // OP_SBC + kCatAlu, // OP_SMLAL + kCatAlu, // OP_SMULL + kCatCoproc | kCatStore, // OP_STC + kCatStore | kCatMultiple, // OP_STM + kCatStore | kCatWord, // OP_STR + kCatStore | kCatByte, // OP_STRB + kCatStore | kCatByte, // OP_STRBT + kCatStore | kCatHalf, // OP_STRH + kCatStore | kCatWord, // OP_STRT + kCatAlu, // OP_SUB + 0, // OP_SWI + kCatLoad | kCatStore, // OP_SWP + kCatLoad | kCatStore | kCatByte, // OP_SWPB + kCatAlu, // OP_TEQ + kCatAlu, // OP_TST + kCatAlu, // OP_UMLAL + kCatAlu, // OP_UMULL + + 0, // OP_THUMB_UNDEFINED, + kCatAlu, // OP_THUMB_ADC, + kCatAlu, // OP_THUMB_ADD, + kCatAlu, // OP_THUMB_AND, + kCatAlu, // OP_THUMB_ASR, + kCatBranch, // OP_THUMB_B, + kCatAlu, // OP_THUMB_BIC, + 0, // OP_THUMB_BKPT, + kCatBranch | kCatBranchLink, // OP_THUMB_BL, + kCatBranch | kCatBranchLink | kCatBranchExch, // OP_THUMB_BLX, + kCatBranch | kCatBranchExch, // OP_THUMB_BX, + kCatAlu, // OP_THUMB_CMN, + kCatAlu, // OP_THUMB_CMP, + kCatAlu, // OP_THUMB_EOR, + kCatLoad | kCatMultiple, // OP_THUMB_LDMIA, + kCatLoad | kCatWord, // OP_THUMB_LDR, + kCatLoad | kCatByte, // OP_THUMB_LDRB, + kCatLoad | kCatHalf, // OP_THUMB_LDRH, + kCatLoad | kCatByte | kCatSigned, // OP_THUMB_LDRSB, + kCatLoad | kCatHalf | kCatSigned, // OP_THUMB_LDRSH, + kCatAlu, // OP_THUMB_LSL, + kCatAlu, // OP_THUMB_LSR, + kCatAlu, // OP_THUMB_MOV, + kCatAlu, // OP_THUMB_MUL, + kCatAlu, // OP_THUMB_MVN, + kCatAlu, // OP_THUMB_NEG, + kCatAlu, // OP_THUMB_ORR, + kCatLoad | kCatMultiple, // OP_THUMB_POP, + kCatStore | kCatMultiple, // OP_THUMB_PUSH, + kCatAlu, // OP_THUMB_ROR, + kCatAlu, // OP_THUMB_SBC, + kCatStore | kCatMultiple, // OP_THUMB_STMIA, + kCatStore | kCatWord, // OP_THUMB_STR, + kCatStore | kCatByte, // OP_THUMB_STRB, + kCatStore | kCatHalf, // OP_THUMB_STRH, + kCatAlu, // OP_THUMB_SUB, + 0, // OP_THUMB_SWI, + kCatAlu, // OP_THUMB_TST, + + 0, // OP_END +}; + +const char *opcode_names[] = { + "invalid", + "undefined", + "adc", + "add", + "and", + "b", + "bl", + "bic", + "bkpt", + "blx", + "bx", + "cdp", + "clz", + "cmn", + "cmp", + "eor", + "ldc", + "ldm", + "ldr", + "ldrb", + "ldrbt", + "ldrh", + "ldrsb", + "ldrsh", + "ldrt", + "mcr", + "mla", + "mov", + "mrc", + "mrs", + "msr", + "mul", + "mvn", + "orr", + "pld", + "rsb", + "rsc", + "sbc", + "smlal", + "smull", + "stc", + "stm", + "str", + "strb", + "strbt", + "strh", + "strt", + "sub", + "swi", + "swp", + "swpb", + "teq", + "tst", + "umlal", + "umull", + + "undefined", + "adc", + "add", + "and", + "asr", + "b", + "bic", + "bkpt", + "bl", + "blx", + "bx", + "cmn", + "cmp", + "eor", + "ldmia", + "ldr", + "ldrb", + "ldrh", + "ldrsb", + "ldrsh", + "lsl", + "lsr", + "mov", + "mul", + "mvn", + "neg", + "orr", + "pop", + "push", + "ror", + "sbc", + "stmia", + "str", + "strb", + "strh", + "sub", + "swi", + "tst", + + NULL +}; diff --git a/emulator/qtools/opcode.h b/emulator/qtools/opcode.h new file mode 100644 index 0000000..c8b67a6 --- /dev/null +++ b/emulator/qtools/opcode.h @@ -0,0 +1,166 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef OPCODE_H +#define OPCODE_H + +#include <inttypes.h> + +// Note: this list of opcodes must match the list used to initialize +// the opflags[] array in opcode.cpp. +enum Opcode { + OP_INVALID, + OP_UNDEFINED, + OP_ADC, + OP_ADD, + OP_AND, + OP_B, + OP_BL, + OP_BIC, + OP_BKPT, + OP_BLX, + OP_BX, + OP_CDP, + OP_CLZ, + OP_CMN, + OP_CMP, + OP_EOR, + OP_LDC, + OP_LDM, + OP_LDR, + OP_LDRB, + OP_LDRBT, + OP_LDRH, + OP_LDRSB, + OP_LDRSH, + OP_LDRT, + OP_MCR, + OP_MLA, + OP_MOV, + OP_MRC, + OP_MRS, + OP_MSR, + OP_MUL, + OP_MVN, + OP_ORR, + OP_PLD, + OP_RSB, + OP_RSC, + OP_SBC, + OP_SMLAL, + OP_SMULL, + OP_STC, + OP_STM, + OP_STR, + OP_STRB, + OP_STRBT, + OP_STRH, + OP_STRT, + OP_SUB, + OP_SWI, + OP_SWP, + OP_SWPB, + OP_TEQ, + OP_TST, + OP_UMLAL, + OP_UMULL, + + // Define thumb opcodes + OP_THUMB_UNDEFINED, + OP_THUMB_ADC, + OP_THUMB_ADD, + OP_THUMB_AND, + OP_THUMB_ASR, + OP_THUMB_B, + OP_THUMB_BIC, + OP_THUMB_BKPT, + OP_THUMB_BL, + OP_THUMB_BLX, + OP_THUMB_BX, + OP_THUMB_CMN, + OP_THUMB_CMP, + OP_THUMB_EOR, + OP_THUMB_LDMIA, + OP_THUMB_LDR, + OP_THUMB_LDRB, + OP_THUMB_LDRH, + OP_THUMB_LDRSB, + OP_THUMB_LDRSH, + OP_THUMB_LSL, + OP_THUMB_LSR, + OP_THUMB_MOV, + OP_THUMB_MUL, + OP_THUMB_MVN, + OP_THUMB_NEG, + OP_THUMB_ORR, + OP_THUMB_POP, + OP_THUMB_PUSH, + OP_THUMB_ROR, + OP_THUMB_SBC, + OP_THUMB_STMIA, + OP_THUMB_STR, + OP_THUMB_STRB, + OP_THUMB_STRH, + OP_THUMB_SUB, + OP_THUMB_SWI, + OP_THUMB_TST, + + OP_END // must be last +}; + +extern uint32_t opcode_flags[]; +extern const char *opcode_names[]; + +// Define bit flags for the opcode categories +static const uint32_t kCatByte = 0x0001; +static const uint32_t kCatHalf = 0x0002; +static const uint32_t kCatWord = 0x0004; +static const uint32_t kCatLong = 0x0008; +static const uint32_t kCatNumBytes = (kCatByte | kCatHalf | kCatWord | kCatLong); +static const uint32_t kCatMultiple = 0x0010; +static const uint32_t kCatSigned = 0x0020; +static const uint32_t kCatLoad = 0x0040; +static const uint32_t kCatStore = 0x0080; +static const uint32_t kCatMemoryRef = (kCatLoad | kCatStore); +static const uint32_t kCatAlu = 0x0100; +static const uint32_t kCatBranch = 0x0200; +static const uint32_t kCatBranchLink = 0x0400; +static const uint32_t kCatBranchExch = 0x0800; +static const uint32_t kCatCoproc = 0x1000; +static const uint32_t kCatLoadMultiple = (kCatLoad | kCatMultiple); +static const uint32_t kCatStoreMultiple = (kCatStore | kCatMultiple); + +inline bool isALU(Opcode op) { return (opcode_flags[op] & kCatAlu) != 0; } +inline bool isBranch(Opcode op) { return (opcode_flags[op] & kCatBranch) != 0; } +inline bool isBranchLink(Opcode op) { + return (opcode_flags[op] & kCatBranchLink) != 0; +} +inline bool isBranchExch(Opcode op) { + return (opcode_flags[op] & kCatBranchExch) != 0; +} +inline bool isLoad(Opcode op) { return (opcode_flags[op] & kCatLoad) != 0; } +inline bool isLoadMultiple(Opcode op) { + return (opcode_flags[op] & kCatLoadMultiple) == kCatLoadMultiple; +} +inline bool isStoreMultiple(Opcode op) { + return (opcode_flags[op] & kCatStoreMultiple) == kCatStoreMultiple; +} +inline bool isStore(Opcode op) { return (opcode_flags[op] & kCatStore) != 0; } +inline bool isSigned(Opcode op) { return (opcode_flags[op] & kCatSigned) != 0; } +inline bool isMemoryRef(Opcode op) { + return (opcode_flags[op] & kCatMemoryRef) != 0; +} +inline int getAccessSize(Opcode op) { return opcode_flags[op] & kCatNumBytes; } +inline bool isCoproc(Opcode op) { return (opcode_flags[op] & kCatCoproc) != 0; } +inline int getNumAccesses(Opcode op, uint32_t binary) { + extern int num_one_bits[]; + int num_accesses = 0; + if (opcode_flags[op] & kCatNumBytes) + num_accesses = 1; + else if (opcode_flags[op] & kCatMultiple) { + num_accesses = num_one_bits[(binary >> 8) & 0xff] + + num_one_bits[binary & 0xff]; + } + return num_accesses; +} + +#endif // OPCODE_H diff --git a/emulator/qtools/parse_options-inl.h b/emulator/qtools/parse_options-inl.h new file mode 100644 index 0000000..f218cc1 --- /dev/null +++ b/emulator/qtools/parse_options-inl.h @@ -0,0 +1,147 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef PARSE_OPTIONS_INL_H +#define PARSE_OPTIONS_INL_H + +// Define a typedef for TraceReaderType and include "parse_options.h" +// before including this header file in a C++ source file. +// +// For example: +// +// struct symbol { +// int elapsed; +// }; +// +// typedef TraceReader<symbol> TraceReaderType; + + +typedef TraceReaderType::symbol_type symbol_type; +typedef TraceReaderType::region_type region_type; +typedef TraceReaderType::ProcessState ProcessState; + +symbol_type *kernel_sym; +symbol_type *library_sym; + +// Returns true if the given event is included (or not excluded) +// from the list of valid events specified by the user on the +// command line. +inline bool IsValidEvent(BBEvent *event, symbol_type *sym) +{ + if (include_some_pids && pid_include_vector.GetBit(event->pid) == 0) + return false; + if (exclude_some_pids && pid_exclude_vector.GetBit(event->pid)) + return false; + if (include_some_procedures) { + if (sym == NULL || included_procedures.Find(sym->name) == 0) + return false; + } + if (exclude_some_procedures) { + if (sym == NULL || excluded_procedures.Find(sym->name)) + return false; + } + return true; +} + +inline symbol_type *GetSymbol(TraceReaderType *trace, int pid, uint32_t addr, + uint64_t time) +{ + symbol_type *sym = trace->LookupFunction(pid, addr, time); + + if (lump_kernel && (sym->region->flags & region_type::kIsKernelRegion)) { + if (kernel_sym == NULL) { + kernel_sym = sym; + sym->name = ":kernel"; + } else { + sym = kernel_sym; + } + } + + if (lump_libraries && (sym->region->flags & region_type::kIsLibraryRegion)) { + if (library_sym == NULL) { + library_sym = sym; + sym->name = ":libs"; + } else { + sym = library_sym; + } + } + + return sym; +} + +inline bool IsIncludedProcedure(symbol_type *sym) +{ + if (include_kernel_syms && (sym->region->flags & region_type::kIsKernelRegion)) + return true; + if (include_library_syms && (sym->region->flags & region_type::kIsLibraryRegion)) + return true; + return included_procedures.Find(sym->name); +} + +inline bool IsExcludedProcedure(symbol_type *sym) +{ + if (exclude_kernel_syms && (sym->region->flags & region_type::kIsKernelRegion)) + return true; + if (exclude_library_syms && (sym->region->flags & region_type::kIsLibraryRegion)) + return true; + return excluded_procedures.Find(sym->name); +} + +// Returns true on end-of-file. +inline bool GetNextValidEvent(TraceReaderType *trace, + BBEvent *event, + BBEvent *first_ignored_event, + symbol_type **sym_ptr) +{ + symbol_type *sym = NULL; + first_ignored_event->time = 0; + if (trace->ReadBB(event)) + return true; + bool recheck = true; + while (recheck) { + recheck = false; + if (include_some_pids) { + while (pid_include_vector.GetBit(event->pid) == 0) { + if (first_ignored_event->time == 0) + *first_ignored_event = *event; + if (trace->ReadBB(event)) + return true; + } + } else if (exclude_some_pids) { + while (pid_exclude_vector.GetBit(event->pid)) { + if (first_ignored_event->time == 0) + *first_ignored_event = *event; + if (trace->ReadBB(event)) + return true; + } + } + + if (include_some_procedures) { + sym = GetSymbol(trace, event->pid, event->bb_addr, event->time); + while (!IsIncludedProcedure(sym)) { + if (first_ignored_event->time == 0) + *first_ignored_event = *event; + if (trace->ReadBB(event)) + return true; + recheck = true; + sym = GetSymbol(trace, event->pid, event->bb_addr, event->time); + } + } else if (exclude_some_procedures) { + sym = GetSymbol(trace, event->pid, event->bb_addr, event->time); + while (IsExcludedProcedure(sym)) { + if (first_ignored_event->time == 0) + *first_ignored_event = *event; + if (trace->ReadBB(event)) + return true; + recheck = true; + sym = GetSymbol(trace, event->pid, event->bb_addr, event->time); + } + } + } + if (sym == NULL) + sym = GetSymbol(trace, event->pid, event->bb_addr, event->time); + + *sym_ptr = sym; + return false; +} + +#endif // PARSE_OPTIONS_INL_H diff --git a/emulator/qtools/parse_options.cpp b/emulator/qtools/parse_options.cpp new file mode 100644 index 0000000..395e9a1 --- /dev/null +++ b/emulator/qtools/parse_options.cpp @@ -0,0 +1,119 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <ctype.h> +#include "bitvector.h" +#include "parse_options.h" +#include "hash_table.h" + +const char *root = ""; +bool lump_kernel; +bool lump_libraries; +Bitvector pid_include_vector(32768); +Bitvector pid_exclude_vector(32768); +bool include_some_pids; +bool exclude_some_pids; + +HashTable<int> excluded_procedures(2000); +HashTable<int> included_procedures(2000); +bool exclude_some_procedures; +bool include_some_procedures; + +bool exclude_kernel_syms; +bool exclude_library_syms; +bool include_kernel_syms; +bool include_library_syms; +bool demangle = true; + +static const char *OptionsUsageStr = + " -e :kernel exclude all kernel symbols\n" + " -e :libs exclude all library symbols\n" + " -e <func> exclude function <func>\n" + " -e <pid> exclude process <pid>\n" + " -i :kernel include all kernel symbols\n" + " -i :libs include all library symbols\n" + " -i <func> include function <func>\n" + " -i <pid> include process <pid>\n" + " -l :kernel lump all the kernel symbols together\n" + " -l :libs lump all the library symbols together\n" + " -m do not demangle C++ symbols (m for 'mangle')\n" + " -r <root> use <root> as the path for finding ELF executables\n" + ; + +void OptionsUsage() +{ + fprintf(stderr, "%s", OptionsUsageStr); +} + +void ParseOptions(int argc, char **argv) +{ + bool err = false; + while (!err) { + int opt = getopt(argc, argv, "+e:i:l:mr:"); + if (opt == -1) + break; + switch (opt) { + case 'e': + if (*optarg == ':') { + if (strcmp(optarg, ":kernel") == 0) + exclude_kernel_syms = true; + else if (strcmp(optarg, ":libs") == 0) + exclude_library_syms = true; + else + err = true; + excluded_procedures.Update(optarg, 1); + exclude_some_procedures = true; + } else if (isdigit(*optarg)) { + int bitnum = atoi(optarg); + pid_exclude_vector.SetBit(bitnum); + exclude_some_pids = true; + } else { + excluded_procedures.Update(optarg, 1); + exclude_some_procedures = true; + } + break; + case 'i': + if (*optarg == ':') { + if (strcmp(optarg, ":kernel") == 0) + include_kernel_syms = true; + else if (strcmp(optarg, ":libs") == 0) + include_library_syms = true; + else + err = true; + included_procedures.Update(optarg, 1); + include_some_procedures = true; + } else if (isdigit(*optarg)) { + int bitnum = atoi(optarg); + pid_include_vector.SetBit(bitnum); + include_some_pids = true; + } else { + included_procedures.Update(optarg, 1); + include_some_procedures = true; + } + break; + case 'l': + if (strcmp(optarg, ":kernel") == 0) + lump_kernel = true; + else if (strcmp(optarg, ":libs") == 0) + lump_libraries = true; + else + err = true; + break; + case 'm': + demangle = false; + break; + case 'r': + root = optarg; + break; + default: + err = true; + break; + } + } + + if (err) { + Usage(argv[0]); + exit(1); + } +} diff --git a/emulator/qtools/parse_options.h b/emulator/qtools/parse_options.h new file mode 100644 index 0000000..aacbb9e --- /dev/null +++ b/emulator/qtools/parse_options.h @@ -0,0 +1,32 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef PARSE_OPTIONS_H +#define PARSE_OPTIONS_H + +#include "bitvector.h" +#include "hash_table.h" + +extern const char *root; +extern bool lump_kernel; +extern bool lump_libraries; +extern Bitvector pid_include_vector; +extern Bitvector pid_exclude_vector; +extern bool include_some_pids; +extern bool exclude_some_pids; + +extern HashTable<int> excluded_procedures; +extern HashTable<int> included_procedures; +extern bool exclude_some_procedures; +extern bool include_some_procedures; + +extern bool exclude_kernel_syms; +extern bool exclude_library_syms; +extern bool include_kernel_syms; +extern bool include_library_syms; +extern bool demangle; + +extern void Usage(const char *program); +extern void ParseOptions(int argc, char **argv); +extern void OptionsUsage(); + +#endif // PARSE_OPTIONS_H diff --git a/emulator/qtools/post_trace.cpp b/emulator/qtools/post_trace.cpp new file mode 100644 index 0000000..99525fb --- /dev/null +++ b/emulator/qtools/post_trace.cpp @@ -0,0 +1,151 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "trace_reader.h" + +typedef struct MyStaticRec { + StaticRec bb; + uint32_t *insns; +} MyStaticRec; + +const int kNumPids = 32768; +char usedPids[kNumPids]; + +int main(int argc, char **argv) { + uint32_t insns[kMaxInsnPerBB]; + + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->SetPostProcessing(true); + trace->Open(trace_filename); + + // Count the number of static basic blocks and instructions. + uint64_t num_static_bb = 0; + uint64_t num_static_insn = 0; + while (1) { + StaticRec static_rec; + + if (trace->ReadStatic(&static_rec)) + break; + if (static_rec.bb_num != num_static_bb) { + fprintf(stderr, + "Error: basic block numbers out of order; expected %lld, got %lld\n", + num_static_bb, static_rec.bb_num); + exit(1); + } + num_static_bb += 1; + num_static_insn += static_rec.num_insns; + trace->ReadStaticInsns(static_rec.num_insns, insns); + } + trace->Close(); + + // Allocate space for all of the static blocks + MyStaticRec *blocks = new MyStaticRec[num_static_bb]; + + // Read the static blocks again and save pointers to them + trace->Open(trace_filename); + for (uint32_t ii = 0; ii < num_static_bb; ++ii) { + trace->ReadStatic(&blocks[ii].bb); + uint32_t num_insns = blocks[ii].bb.num_insns; + if (num_insns > 0) { + blocks[ii].insns = new uint32_t[num_insns]; + trace->ReadStaticInsns(num_insns, blocks[ii].insns); + } + } + + // Check the last basic block. If it contains a special undefined + // instruction, then truncate the basic block at that point. + uint32_t num_insns = blocks[num_static_bb - 1].bb.num_insns; + uint32_t *insn_ptr = blocks[num_static_bb - 1].insns; + for (uint32_t ii = 0; ii < num_insns; ++ii, ++insn_ptr) { + if (*insn_ptr == 0xe6c00110) { + uint32_t actual_num_insns = ii + 1; + blocks[num_static_bb - 1].bb.num_insns = actual_num_insns; + num_static_insn -= (num_insns - actual_num_insns); + + // Write the changes back to the trace file + trace->TruncateLastBlock(actual_num_insns); + break; + } + } + TraceHeader *header = trace->GetHeader(); + strcpy(header->ident, TRACE_IDENT); + header->num_static_bb = num_static_bb; + header->num_dynamic_bb = 0; + header->num_static_insn = num_static_insn; + header->num_dynamic_insn = 0; + trace->WriteHeader(header); + + // Reopen the trace file in order to force the trace manager to reread + // the static blocks now that we have written that information to the + // header. + trace->Close(); + trace->Open(trace_filename); + + // Count the number of dynamic executions of basic blocks and instructions. + // Also keep track of which process ids are used. + uint64_t num_dynamic_bb = 0; + uint64_t num_dynamic_insn = 0; + while (1) { + BBEvent event; + + if (trace->ReadBB(&event)) + break; + if (event.bb_num >= num_static_bb) { + fprintf(stderr, + "Error: basic block number (%lld) too large (num blocks: %lld)\n", + event.bb_num, num_static_bb); + exit(1); + } + usedPids[event.pid] = 1; + num_dynamic_bb += 1; + num_dynamic_insn += event.num_insns; + } + + // Count the number of process ids that are used and remember the first + // unused pid. + int numUsedPids = 0; + int unusedPid = -1; + for (int pid = 0; pid < kNumPids; pid++) { + if (usedPids[pid] == 1) { + numUsedPids += 1; + } else if (unusedPid == -1) { + unusedPid = pid; + } + } + + // Rewrite the header with the dynamic counts + header->num_dynamic_bb = num_dynamic_bb; + header->num_dynamic_insn = num_dynamic_insn; + header->num_used_pids = numUsedPids; + header->first_unused_pid = unusedPid; + trace->WriteHeader(header); + trace->Close(); + + printf("Static basic blocks: %llu, Dynamic basic blocks: %llu\n", + num_static_bb, num_dynamic_bb); + printf("Static instructions: %llu, Dynamic instructions: %llu\n", + num_static_insn, num_dynamic_insn); + + double elapsed_secs = header->elapsed_usecs / 1000000.0; + double insn_per_sec = 0; + if (elapsed_secs != 0) + insn_per_sec = num_dynamic_insn / elapsed_secs; + char *suffix = ""; + if (insn_per_sec >= 1000000) { + insn_per_sec /= 1000000.0; + suffix = "M"; + } else if (insn_per_sec > 1000) { + insn_per_sec /= 1000.0; + suffix = "K"; + } + printf("Elapsed seconds: %.2f, simulated instructions/sec: %.1f%s\n", + elapsed_secs, insn_per_sec, suffix); + return 0; +} diff --git a/emulator/qtools/profile_pid.cpp b/emulator/qtools/profile_pid.cpp new file mode 100644 index 0000000..aa37847 --- /dev/null +++ b/emulator/qtools/profile_pid.cpp @@ -0,0 +1,93 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> +#include "trace_reader.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +// This function is called from quicksort to compare the cpu time +// of processes and sort into decreasing order. +int cmp_dec_cpu_time(const void *a, const void *b) { + ProcessState *proc1, *proc2; + + proc1 = (ProcessState*)a; + proc2 = (ProcessState*)b; + if (proc1 == NULL) { + if (proc2 == NULL) + return 0; + return 1; + } + if (proc2 == NULL) + return -1; + if (proc1->cpu_time < proc2->cpu_time) + return 1; + if (proc1->cpu_time > proc2->cpu_time) + return -1; + // If the cpu_time times are the same, then sort into increasing + // order of pid. + return proc1->pid - proc2->pid; +} + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file\n", program); + OptionsUsage(); +} + +int main(int argc, char **argv) { + // Parse the options + ParseOptions(argc, argv); + if (argc - optind != 1) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind]; + TraceReader<> *trace = new TraceReader<>; + trace->Open(trace_filename); + trace->SetRoot(root); + + while (1) { + BBEvent event, ignored; + symbol_type *dummy_sym; + + if (GetNextValidEvent(trace, &event, &ignored, &dummy_sym)) + break; + } + + int num_procs; + ProcessState *processes = trace->GetProcesses(&num_procs); + qsort(processes, num_procs, sizeof(ProcessState), cmp_dec_cpu_time); + + uint64_t total_time = 0; + for (int ii = 0; ii < num_procs; ++ii) { + total_time += processes[ii].cpu_time; + } + + uint64_t sum_time = 0; + printf(" pid parent cpu_time %% %% flags argv\n"); + ProcessState *pstate = &processes[0]; + for (int ii = 0; ii < num_procs; ++ii, ++pstate) { + sum_time += pstate->cpu_time; + double per = 100.0 * pstate->cpu_time / total_time; + double sum_per = 100.0 * sum_time / total_time; + char *print_flags = ""; + if ((pstate->flags & ProcessState::kCalledExec) == 0) + print_flags = "T"; + if (pstate->name == NULL) + pstate->name = ""; + printf("%5d %5d %10llu %6.2f %6.2f %5s %s", + pstate->pid, pstate->parent_pid, pstate->cpu_time, + per, sum_per, print_flags, pstate->name); + for (int ii = 1; ii < pstate->argc; ++ii) { + printf(" %s", pstate->argv[ii]); + } + printf("\n"); + } + return 0; +} diff --git a/emulator/qtools/profile_trace.cpp b/emulator/qtools/profile_trace.cpp new file mode 100644 index 0000000..9d14a03 --- /dev/null +++ b/emulator/qtools/profile_trace.cpp @@ -0,0 +1,131 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "trace_reader.h" +#include "parse_options.h" + +const int kMillion = 1000000; +const int kMHz = 200 * kMillion; + +struct symbol { + int count; // number of times a function is executed + uint64_t elapsed; // elapsed time for this function +}; + +typedef TraceReader<symbol> TraceReaderType; + +#include "parse_options-inl.h" + +static const uint32_t kOffsetThreshold = 0x100000; + +// This comparison function is called from qsort() to sort +// symbols into decreasing elapsed time. +int cmp_sym_elapsed(const void *a, const void *b) { + const symbol_type *syma, *symb; + uint64_t elapsed1, elapsed2; + + syma = static_cast<symbol_type const *>(a); + symb = static_cast<symbol_type const *>(b); + elapsed1 = syma->elapsed; + elapsed2 = symb->elapsed; + if (elapsed1 < elapsed2) + return 1; + if (elapsed1 == elapsed2) + return strcmp(syma->name, symb->name); + return -1; +} + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file\n", program); + OptionsUsage(); +} + +int main(int argc, char **argv) +{ + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<symbol> *trace = new TraceReader<symbol>; + trace->Open(trace_filename); + trace->SetDemangle(demangle); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + symbol_type dummy; + dummy.count = 0; + dummy.elapsed = 0; + symbol_type *prev_sym = &dummy; + uint64_t prev_bb_time = 0; + while (1) { + symbol_type *sym; + BBEvent event; + BBEvent first_ignored_event; + + bool eof = GetNextValidEvent(trace, &event, &first_ignored_event, &sym); + + // Assign the elapsed time to the previous function symbol + uint64_t elapsed = 0; + if (first_ignored_event.time != 0) + elapsed = first_ignored_event.time - prev_bb_time; + else if (!eof) + elapsed = event.time - prev_bb_time; + prev_sym->elapsed += elapsed; + + if (eof) + break; + + prev_bb_time = event.time; + sym->count += 1; + prev_sym = sym; +#if 0 + printf("t%lld bb_num: %d, bb_addr: 0x%x func: %s, addr: 0x%x, count: %d\n", + bb_time, bb_num, bb_addr, sym->name, sym->addr, sym->count); +#endif + } + + int nsyms; + symbol_type *syms = trace->GetSymbols(&nsyms); + + // Sort the symbols into decreasing order of elapsed time + qsort(syms, nsyms, sizeof(symbol_type), cmp_sym_elapsed); + + // Add up all the cycles + uint64_t total = 0; + symbol_type *sym = syms; + for (int ii = 0; ii < nsyms; ++ii, ++sym) { + total += sym->elapsed; + } + + double secs = 1.0 * total / kMHz; + printf("Total seconds: %.2f, total cycles: %lld, MHz: %d\n\n", + secs, total, kMHz / kMillion); + + uint64_t sum = 0; + printf("Elapsed secs Elapsed cyc %% %% Function\n"); + sym = syms; + for (int ii = 0; ii < nsyms; ++ii, ++sym) { + if (sym->elapsed == 0) + break; + sum += sym->elapsed; + double per = 100.0 * sym->elapsed / total; + double sum_per = 100.0 * sum / total; + double secs = 1.0 * sym->elapsed / kMHz; + char *ksym = " "; + if (sym->region->flags & region_type::kIsKernelRegion) + ksym = "k"; + printf("%12.2f %11lld %6.2f %6.2f %s %s\n", + secs, sym->elapsed, per, sum_per, ksym, sym->name); + } + delete[] syms; + delete trace; + + return 0; +} diff --git a/emulator/qtools/q2dm.cpp b/emulator/qtools/q2dm.cpp new file mode 100644 index 0000000..7f987dc --- /dev/null +++ b/emulator/qtools/q2dm.cpp @@ -0,0 +1,275 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "bitvector.h" +#include "parse_options.h" +#include "dmtrace.h" +#include "armdis.h" + +struct symbol { + uint32_t id; +}; + +typedef TraceReader<symbol> TraceReaderType; + +#include "parse_options-inl.h" +#include "callstack.h" + +DmTrace *dmtrace; + +class MyFrame : public StackFrame<symbol_type> { + public: + void push(int stackLevel, uint64_t time, CallStackBase *base); + void pop(int stackLevel, uint64_t time, CallStackBase *base); +}; + +typedef CallStack<MyFrame> CallStackType; + +static const int kNumStackFrames = 500; +static const int kMaxThreads = (32 * 1024); +uint64_t thread_time[kMaxThreads]; + +class FunctionStack { + public: + FunctionStack() { + top = 0; + } + void push(symbol_type *sym) { + if (top >= kNumStackFrames) + return; + frames[top] = sym; + top += 1; + } + + symbol_type* pop() { + if (top <= 0) { + return NULL; + } + top -= 1; + return frames[top]; + } + + void showStack() { + fprintf(stderr, "top %d\n", top); + for (int ii = 0; ii < top; ii++) { + fprintf(stderr, " %d: %s\n", ii, frames[ii]->name); + } + } + + private: + int top; + symbol_type *frames[kNumStackFrames]; +}; + +FunctionStack *dmtrace_stack[kMaxThreads]; + +void MyFrame::push(int stackLevel, uint64_t time, CallStackBase *base) +{ + int pid = base->getId(); + CallStackType *stack = (CallStackType *) base; + +#if 0 + fprintf(stderr, "native push t %llu p %d s %d fid %d 0x%x %s\n", + stack->getGlobalTime(time), pid, stackLevel, + function->id, function->addr, function->name); +#endif + + FunctionStack *fstack = dmtrace_stack[pid]; + if (fstack == NULL) { + fstack = new FunctionStack(); + dmtrace_stack[pid] = fstack; + } + + fstack->push(function); + thread_time[pid] = time; + dmtrace->addFunctionEntry(function->id, time, pid); +} + +void MyFrame::pop(int stackLevel, uint64_t time, CallStackBase *base) +{ + int pid = base->getId(); + CallStackType *stack = (CallStackType *) base; + +#if 0 + fprintf(stderr, "native pop t %llu p %d s %d fid %d 0x%x %s\n", + stack->getGlobalTime(time), pid, stackLevel, + function->id, function->addr, function->name); +#endif + + FunctionStack *fstack = dmtrace_stack[pid]; + if (fstack == NULL) { + fstack = new FunctionStack(); + dmtrace_stack[pid] = fstack; + } + + symbol_type *sym = fstack->pop(); + if (sym != NULL && sym != function) { + fprintf(stderr, "Error: q2dm function mismatch at time %llu pid %d sym %s\n", + stack->getGlobalTime(time), pid, sym->name); + fstack->showStack(); + exit(1); + } + + thread_time[pid] = time; + dmtrace->addFunctionExit(function->id, time, pid); +} + +uint32_t nextFunctionId = 4; +CallStackType *stacks[kMaxThreads]; + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_name elf_file dmtrace_name\n", + program); + OptionsUsage(); +} + +int main(int argc, char **argv) +{ + bool useKernelStack = true; + + ParseOptions(argc, argv); + if (argc - optind != 3) { + Usage(argv[0]); + exit(1); + } + + char *qemu_trace_file = argv[optind++]; + char *elf_file = argv[optind++]; + char *dmtrace_file = argv[optind++]; + TraceReaderType *trace = new TraceReaderType; + trace->Open(qemu_trace_file); + trace->SetDemangle(demangle); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + TraceHeader *qheader = trace->GetHeader(); + uint64_t startTime = qheader->start_sec; + startTime = (startTime << 32) | qheader->start_usec; + int kernelPid = qheader->first_unused_pid; + + dmtrace = new DmTrace; + dmtrace->open(dmtrace_file, startTime); + + bool inKernel = false; + CallStackType *kernelStack = NULL; + if (useKernelStack) { + // Create a fake kernel thread stack where we will put all the kernel + // code. + kernelStack = new CallStackType(kernelPid, kNumStackFrames, trace); + dmtrace->addThread(kernelPid, "(kernel)"); + } + + CallStackType *prevStack = NULL; + BBEvent event; + while (1) { + BBEvent ignored; + symbol_type *function; + + if (GetNextValidEvent(trace, &event, &ignored, &function)) + break; + if (event.bb_num == 0) + break; +#if 0 + fprintf(stderr, "event t %llu p %d %s\n", + event.time, event.pid, function->name); +#endif + + CallStackType *pStack; + if (useKernelStack) { + uint32_t flags = function->region->flags; + uint32_t region_mask = region_type::kIsKernelRegion + | region_type::kIsUserMappedRegion; + if ((flags & region_mask) == region_type::kIsKernelRegion) { + // Use the kernel stack + pStack = kernelStack; + inKernel = true; + } else { + // If we were just in the kernel then pop off all of the + // stack frames for the kernel thread. + if (inKernel == true) { + inKernel = false; + kernelStack->popAll(event.time); + } + + // Get the stack for the current thread + pStack = stacks[event.pid]; + } + } else { + // Get the stack for the current thread + pStack = stacks[event.pid]; + } + + // If the stack does not exist, then allocate a new one. + if (pStack == NULL) { + pStack = new CallStackType(event.pid, kNumStackFrames, trace); + stacks[event.pid] = pStack; + char *name = trace->GetProcessName(event.pid); + dmtrace->addThread(event.pid, name); + } + + if (prevStack != pStack) { + pStack->threadStart(event.time); + if (prevStack) + prevStack->threadStop(event.time); + } + prevStack = pStack; + + // If we have never seen this function before, then add it to the + // list of known functions. + if (function->id == 0) { + function->id = nextFunctionId; + nextFunctionId += 4; + uint32_t flags = function->region->flags; + const char *name = function->name; + if (flags & region_type::kIsKernelRegion) { + // To distinguish kernel function names from user library + // names, add a marker to the name. + int len = strlen(name) + strlen(" [kernel]") + 1; + char *kernelName = new char[len]; + strcpy(kernelName, name); + strcat(kernelName, " [kernel]"); + name = kernelName; + } + dmtrace->parseAndAddFunction(function->id, name); + } + + // Update the stack + pStack->updateStack(&event, function); + } + + if (prevStack == NULL) { + fprintf(stderr, "Error: no events in trace.\n"); + exit(1); + } + prevStack->threadStop(event.time); + for (int ii = 0; ii < kMaxThreads; ++ii) { + if (stacks[ii]) { + stacks[ii]->threadStart(event.time); + stacks[ii]->popAll(event.time); + } + } + if (useKernelStack) { + kernelStack->popAll(event.time); + } + + // Read the pid events to find the names of the processes + while (1) { + PidEvent pid_event; + if (trace->ReadPidEvent(&pid_event)) + break; + if (pid_event.rec_type == kPidName) { + dmtrace->updateName(pid_event.pid, pid_event.path); + } + } + + + dmtrace->close(); + delete dmtrace; + delete trace; + return 0; +} diff --git a/emulator/qtools/q2g.cpp b/emulator/qtools/q2g.cpp new file mode 100644 index 0000000..6b2ae92 --- /dev/null +++ b/emulator/qtools/q2g.cpp @@ -0,0 +1,108 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "gtrace.h" +#include "bitvector.h" +#include "parse_options.h" + +struct symbol { + int filenum; // the file number (for gtrace) + int procnum; // the procedure number (for gtrace) +}; + +typedef TraceReader<symbol> TraceReaderType; + +#include "parse_options-inl.h" + +const int kMaxProcNum = 4095; +int next_filenum = 1; +int next_procnum = 1; + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_file elf_file gtrace_file\n", + program); + OptionsUsage(); +} + +int main(int argc, char **argv) +{ + ParseOptions(argc, argv); + if (argc - optind != 3) { + Usage(argv[0]); + exit(1); + } + + char *qemu_trace_file = argv[optind++]; + char *elf_file = argv[optind++]; + char *gtrace_file = argv[optind++]; + TraceReader<symbol> *trace = new TraceReader<symbol>; + trace->Open(qemu_trace_file); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + TraceHeader *qheader = trace->GetHeader(); + + // Get the first valid event to get the process id for the gtrace header. + BBEvent event; + BBEvent ignored; + symbol_type *sym; + if (GetNextValidEvent(trace, &event, &ignored, &sym)) + return 0; + + Gtrace *gtrace = new Gtrace; + gtrace->Open(gtrace_file, qheader->pdate, qheader->ptime); + gtrace->WriteFirstHeader(qheader->start_sec, event.pid); + + symbol_type *prev_sym = NULL; + bool eof = false; + while (!eof) { + if (sym != prev_sym) { + // This procedure is different from the previous procedure. + + // If we have never seen this symbol before, then add it to the + // list of known procedures. + if (sym->filenum == 0) { + sym->filenum = next_filenum; + sym->procnum = next_procnum; + gtrace->AddProcedure(sym->filenum, sym->procnum, sym->name); + next_procnum += 1; + if (next_procnum > kMaxProcNum) { + next_filenum += 1; + next_procnum = 1; + } + } + + // If we haven't yet recorded the procedure exit for the previous + // procedure, then do it now. + if (prev_sym) { + gtrace->AddProcExit(prev_sym->filenum, prev_sym->procnum, event.time, + event.pid); + } + + // If this is not the terminating record, then record a procedure + // entry. + if (event.bb_num != 0) { + gtrace->AddProcEntry(sym->filenum, sym->procnum, event.time, event.pid); + prev_sym = sym; + } + } + + eof = GetNextValidEvent(trace, &event, &ignored, &sym); + if (ignored.time != 0 && prev_sym) { + // We read an event that we are ignoring. + // If we haven't already recorded a procedure exit, then do so. + gtrace->AddProcExit(prev_sym->filenum, prev_sym->procnum, ignored.time, + ignored.pid); + prev_sym = NULL; + } + } + + delete gtrace; + delete trace; + return 0; +} diff --git a/emulator/qtools/read_addr.cpp b/emulator/qtools/read_addr.cpp new file mode 100644 index 0000000..38fc62a --- /dev/null +++ b/emulator/qtools/read_addr.cpp @@ -0,0 +1,29 @@ +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include "trace_reader.h" + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->Open(trace_filename); + + while (1) { + uint64_t time; + uint32_t addr; + int flags; + + if (trace->ReadAddr(&time, &addr, &flags)) + break; + char *op = "ld"; + if (flags == 1) + op = "st"; + printf("%lld 0x%08x %s\n", time, addr, op); + } + return 0; +} diff --git a/emulator/qtools/read_elf.cpp b/emulator/qtools/read_elf.cpp new file mode 100644 index 0000000..1d1a74a --- /dev/null +++ b/emulator/qtools/read_elf.cpp @@ -0,0 +1,210 @@ +/************************************************************************* + Copyright (C) 2002,2003,2004,2005 Wei Qin + See file COPYING for more information. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. +*************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include "read_elf.h" + +#define SwapHalf(a) (((a & 0x00ff) << 8) | ((a & 0xff00) >> 8)) +#define SwapWord(a) (((a & 0xff000000) >> 24) | ((a & 0x00ff0000) >> 8) | ((a & 0x0000ff00) << 8) | ((a & 0x000000ff) << 24)) +#define SwapAddr(a) SwapWord(a) +#define SwapOff(a) SwapWord(a) +#define SwapSection(a) SwapHalf(a) + +int LittleEndian() +{ + Elf32_Word a = 0x01020304; + return *(char *) &a == 0x04; +} + +void SwapElfHeader(Elf32_Ehdr *hdr) +{ + hdr->e_type = SwapHalf(hdr->e_type); + hdr->e_machine = SwapHalf(hdr->e_machine); + hdr->e_version = SwapWord(hdr->e_version); + hdr->e_entry = SwapAddr(hdr->e_entry); + hdr->e_phoff = SwapOff(hdr->e_phoff); + hdr->e_shoff = SwapOff(hdr->e_shoff); + hdr->e_flags = SwapWord(hdr->e_flags); + hdr->e_ehsize = SwapHalf(hdr->e_ehsize); + hdr->e_phentsize = SwapHalf(hdr->e_phentsize); + hdr->e_phnum = SwapHalf(hdr->e_phnum); + hdr->e_shentsize = SwapHalf(hdr->e_shentsize); + hdr->e_shnum = SwapHalf(hdr->e_shnum); + hdr->e_shstrndx = SwapHalf(hdr->e_shstrndx); +} + +void SwapSectionHeader(Elf32_Shdr *shdr) +{ + shdr->sh_name = SwapWord(shdr->sh_name); + shdr->sh_type = SwapWord(shdr->sh_type); + shdr->sh_flags = SwapWord(shdr->sh_flags); + shdr->sh_addr = SwapAddr(shdr->sh_addr); + shdr->sh_offset = SwapOff(shdr->sh_offset); + shdr->sh_size = SwapWord(shdr->sh_size); + shdr->sh_link = SwapWord(shdr->sh_link); + shdr->sh_info = SwapWord(shdr->sh_info); + shdr->sh_addralign = SwapWord(shdr->sh_addralign); + shdr->sh_entsize = SwapWord(shdr->sh_entsize); +} + +void SwapElfSymbol(Elf32_Sym *sym) +{ + sym->st_name = SwapWord(sym->st_name); + sym->st_value = SwapAddr(sym->st_value); + sym->st_size = SwapWord(sym->st_size); + sym->st_shndx = SwapSection(sym->st_shndx); +} + +void AdjustElfHeader(Elf32_Ehdr *hdr) +{ + switch(hdr->e_ident[EI_DATA]) + { + case ELFDATA2LSB: + if (!LittleEndian()) + SwapElfHeader(hdr); + break; + case ELFDATA2MSB: + if (LittleEndian()) + SwapElfHeader(hdr); + break; + } +} + +void AdjustSectionHeader(Elf32_Ehdr *hdr, Elf32_Shdr *shdr) +{ + switch(hdr->e_ident[EI_DATA]) + { + case ELFDATA2LSB: + if (!LittleEndian()) + SwapSectionHeader(shdr); + break; + case ELFDATA2MSB: + if (LittleEndian()) + SwapSectionHeader(shdr); + break; + } +} + +void AdjustElfSymbols(Elf32_Ehdr *hdr, Elf32_Sym *elf_symbols, int num_entries) +{ + if (hdr->e_ident[EI_DATA] == ELFDATA2LSB && LittleEndian()) + return; + if (hdr->e_ident[EI_DATA] == ELFDATA2MSB && !LittleEndian()) + return; + for (int ii = 0; ii < num_entries; ++ii) { + SwapElfSymbol(&elf_symbols[ii]); + } +} + +Elf32_Ehdr *ReadElfHeader(FILE *fobj) +{ + Elf32_Ehdr *hdr = new Elf32_Ehdr; + int rval = fread(hdr, sizeof(Elf32_Ehdr), 1, fobj); + if (rval != 1) { + delete hdr; + return NULL; + } + if (hdr->e_ident[EI_MAG0] != 0x7f || hdr->e_ident[EI_MAG1] != 'E' || + hdr->e_ident[EI_MAG2] != 'L' || hdr->e_ident[EI_MAG3] != 'F') { + delete hdr; + return NULL; + } + AdjustElfHeader(hdr); + return hdr; +} + +Elf32_Shdr *ReadSectionHeaders(Elf32_Ehdr *hdr, FILE *f) +{ + int i; + unsigned long sz = hdr->e_shnum * hdr->e_shentsize; + assert(sizeof(Elf32_Shdr) == hdr->e_shentsize); + Elf32_Shdr *shdr = new Elf32_Shdr[hdr->e_shnum]; + + if (fseek(f, hdr->e_shoff, SEEK_SET) != 0) + { + delete[] shdr; + return NULL; + } + if (fread(shdr, sz, 1, f) != 1) + { + delete[] shdr; + return NULL; + } + + for(i = 0; i < hdr->e_shnum; i++) + AdjustSectionHeader(hdr, shdr + i); + + return shdr; +} + + +char *ReadStringTable(Elf32_Ehdr *hdr, Elf32_Shdr *shdr_table, FILE *f) +{ + Elf32_Shdr *shdr = shdr_table + hdr->e_shstrndx; + char *string_table; + + string_table = new char[shdr->sh_size]; + fseek(f, shdr->sh_offset, SEEK_SET); + fread(string_table, shdr->sh_size, 1, f); + + return string_table; +} + +int ReadSection(Elf32_Shdr *shdr, void *buffer, FILE *f) +{ + if (fseek(f, shdr->sh_offset, SEEK_SET) != 0) + return -1; + if (fread(buffer, shdr->sh_size, 1, f) != 1) + return -1; + return 0; +} + +char *GetSymbolName(Elf32_Half index, char *string_table) +{ + return string_table + index; +} + +Elf32_Shdr *FindSymbolTableSection(Elf32_Ehdr *hdr, + Elf32_Shdr *shdr, + char *string_table) +{ + for(int ii = 0; ii < hdr->e_shnum; ii++) { + if (shdr[ii].sh_type == SHT_SYMTAB && + strcmp(GetSymbolName(shdr[ii].sh_name, string_table), + ".symtab") == 0) + { + return &shdr[ii]; + } + } + return NULL; +} + +Elf32_Shdr *FindSymbolStringTableSection(Elf32_Ehdr *hdr, + Elf32_Shdr *shdr, + char *string_table) +{ + for(int ii = 0; ii < hdr->e_shnum; ii++) { + if (shdr[ii].sh_type == SHT_STRTAB && + strcmp(GetSymbolName(shdr[ii].sh_name, string_table), + ".strtab") == 0) + { + return &shdr[ii]; + } + } + return NULL; +} diff --git a/emulator/qtools/read_elf.h b/emulator/qtools/read_elf.h new file mode 100644 index 0000000..72266c3 --- /dev/null +++ b/emulator/qtools/read_elf.h @@ -0,0 +1,20 @@ +#ifndef READ_ELF_H +#define READ_ELF_H + +#include <stdio.h> +#include <elf.h> + +Elf32_Ehdr *ReadElfHeader(FILE *fobj); +Elf32_Shdr *ReadSectionHeaders(Elf32_Ehdr *hdr, FILE *fobj); +char *ReadStringTable(Elf32_Ehdr *hdr, Elf32_Shdr *shdr, FILE *fobj); +Elf32_Shdr *FindSymbolTableSection(Elf32_Ehdr *hdr, + Elf32_Shdr *shdr, + char *string_table); +Elf32_Shdr *FindSymbolStringTableSection(Elf32_Ehdr *hdr, + Elf32_Shdr *shdr, + char *string_table); +int ReadSection(Elf32_Shdr *shdr, void *buffer, FILE *f); +void AdjustElfSymbols(Elf32_Ehdr *hdr, Elf32_Sym *elf_symbols, + int num_entries); + +#endif /* READ_ELF_H */ diff --git a/emulator/qtools/read_method.cpp b/emulator/qtools/read_method.cpp new file mode 100644 index 0000000..e48edad --- /dev/null +++ b/emulator/qtools/read_method.cpp @@ -0,0 +1,52 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include "trace_reader.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_name elf_file\n", + program); + OptionsUsage(); +} + +int main(int argc, char **argv) { + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *qemu_trace_file = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReaderType *trace = new TraceReaderType; + trace->Open(qemu_trace_file); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + while (1) { + MethodRec method_record; + symbol_type *sym; + TraceReaderType::ProcessState *proc; + + if (trace->ReadMethodSymbol(&method_record, &sym, &proc)) + break; + if (sym != NULL) { + printf("%lld p %d 0x%x %d %s\n", + method_record.time, proc->pid, method_record.addr, + method_record.flags, sym->name); + } else { + printf("%lld p %d 0x%x %d\n", + method_record.time, proc->pid, method_record.addr, + method_record.flags); + } + proc->DumpStack(); + } + return 0; +} diff --git a/emulator/qtools/read_pid.cpp b/emulator/qtools/read_pid.cpp new file mode 100644 index 0000000..a2d69d4 --- /dev/null +++ b/emulator/qtools/read_pid.cpp @@ -0,0 +1,71 @@ +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include "trace_reader.h" + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Usage: %s trace_file\n", argv[0]); + exit(1); + } + + char *trace_filename = argv[1]; + TraceReaderBase *trace = new TraceReaderBase; + trace->Open(trace_filename); + + while (1) { + PidEvent event; + if (trace->ReadPidEvent(&event)) + break; + switch (event.rec_type) { + case kPidFork: + printf("t%lld fork tgid %d pid %d\n", event.time, event.tgid, event.pid); + break; + case kPidClone: + printf("t%lld clone tgid %d pid %d\n", event.time, event.tgid, event.pid); + break; + case kPidSwitch: + printf("t%lld switch %d\n", event.time, event.pid); + break; + case kPidExit: + printf("t%lld exit %d\n", event.time, event.pid); + break; + case kPidMmap: + printf("t%lld mmap %08x - %08x, offset %08x '%s'\n", + event.time, event.vstart, event.vend, event.offset, event.path); + delete[] event.path; + break; + case kPidMunmap: + printf("t%lld munmap %08x - %08x\n", + event.time, event.vstart, event.vend); + break; + case kPidSymbolAdd: + printf("t%lld add sym %08x '%s'\n", + event.time, event.vstart, event.path); + delete[] event.path; + break; + case kPidSymbolRemove: + printf("t%lld remove %08x\n", event.time, event.vstart); + break; + case kPidExec: + printf("t%lld argc: %d\n", event.time, event.argc); + for (int ii = 0; ii < event.argc; ++ii) { + printf(" argv[%d]: %s\n", ii, event.argv[ii]); + delete[] event.argv[ii]; + } + delete[] event.argv; + break; + case kPidKthreadName: + printf("t%lld kthread tgid %d pid %d %s\n", + event.time, event.tgid, event.pid, event.path); + delete[] event.path; + break; + case kPidName: + printf("t%lld name %d %s\n", + event.time, event.pid, event.path); + delete[] event.path; + break; + } + } + return 0; +} diff --git a/emulator/qtools/read_trace.cpp b/emulator/qtools/read_trace.cpp new file mode 100644 index 0000000..fb4917c --- /dev/null +++ b/emulator/qtools/read_trace.cpp @@ -0,0 +1,165 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "armdis.h" +#include "parse_options.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" + +static const uint32_t kOffsetThreshold = 0x100000; +static uint64_t startTime = 0; + +void Usage(const char *program) +{ + fprintf(stderr, + "Usage: %s [options] [-- -s start_time] trace_file elf_file\n", + program); + OptionsUsage(); +} + + +bool localParseOptions(int argc, char **argv) +{ + bool err = false; + while (!err) { + int opt = getopt(argc, argv, "+s:"); + if (opt == -1) + break; + switch (opt) { + case 's': + startTime = strtoull(optarg, NULL, 0); + break; + default: + err = true; + break; + } + } + return err; +} + +int main(int argc, char **argv) { + // Parse the options + ParseOptions(argc, argv); + localParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *trace_filename = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReader<> *trace = new TraceReader<>; + trace->Open(trace_filename); + trace->SetDemangle(demangle); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + + while (1) { + symbol_type *sym; + char buf[1024]; + BBEvent event; + BBEvent ignored; + + if (GetNextValidEvent(trace, &event, &ignored, &sym)) + break; +#if 0 + fprintf(stderr, "t%llu bb %lld %d\n", + event.time, event.bb_num, event.num_insns); +#endif + + uint32_t *insns = event.insns; + uint32_t addr = event.bb_addr; + uint32_t offset = addr - sym->addr - sym->region->base_addr; + symbol_type *vm_sym = sym->vm_sym; + const char *vm_name = NULL; + if (vm_sym != NULL) { + vm_name = vm_sym->name; + offset = addr - vm_sym->addr - vm_sym->region->base_addr; + } +#if 0 + if (strcmp(sym->name, "(unknown)") == 0 || offset > kOffsetThreshold) { + ProcessState *process = trace->GetCurrentProcess(); + ProcessState *manager = process->addr_manager; + for (int ii = 0; ii < manager->nregions; ++ii) { + printf(" %2d: %08x - %08x base: %08x offset: %u nsyms: %4d flags: 0x%x %s\n", + ii, + manager->regions[ii]->vstart, + manager->regions[ii]->vend, + manager->regions[ii]->base_addr, + manager->regions[ii]->file_offset, + manager->regions[ii]->nsymbols, + manager->regions[ii]->flags, + manager->regions[ii]->path); + int nsymbols = manager->regions[ii]->nsymbols; + for (int jj = 0; jj < 10 && jj < nsymbols; ++jj) { + printf(" %08x %s\n", + manager->regions[ii]->symbols[jj].addr, + manager->regions[ii]->symbols[jj].name); + } + } + } +#endif +#if 1 + for (int ii = 0; ii < event.num_insns; ++ii) { + uint64_t sim_time = trace->ReadInsnTime(event.time); + if (sim_time < startTime) + continue; + + uint32_t insn = insns[ii]; + char *disasm; + int bytes; + if (vm_name != NULL) { + sprintf(buf, "%s+%02x: %s", vm_name, offset, sym->name); + } else { + sprintf(buf, "%s+%02x", sym->name, offset); + } + + if (insn_is_thumb(insn)) { + bytes = 2; + insn = insn_unwrap_thumb(insn); + + // thumb_pair is true if this is the first of a pair of + // thumb instructions (BL or BLX). + bool thumb_pair = ((insn & 0xf800) == 0xf000); + + // Get the next thumb instruction (if any) because we may need + // it for the case where insn is BL or BLX. + uint32_t insn2 = 0; + if (thumb_pair && (ii + 1 < event.num_insns)) { + insn2 = insns[ii + 1]; + insn2 = insn_unwrap_thumb(insn2); + bytes = 4; + ii += 1; + } + disasm = disasm_insn_thumb(addr, insn, insn2, NULL); + if (thumb_pair) { + printf("%llu p%-4d %08x %04x %04x %-30s %s\n", + sim_time, event.pid, addr, insn, insn2, buf, disasm); + } else { + printf("%llu p%-4d %08x %04x %-30s %s\n", + sim_time, event.pid, addr, insn, buf, disasm); + } + } else { + bytes = 4; + disasm = Arm::disasm(addr, insn, NULL); + printf("%llu p%-4d %08x %08x %-30s %s\n", + sim_time, event.pid, addr, insn, buf, disasm); + } + //printf("t%llu \t%08x\n", sim_time, addr); + addr += bytes; + offset += bytes; + } +#endif +#if 0 + assert(offset < kOffsetThreshold); +#endif + } + + delete trace; + return 0; +} diff --git a/emulator/qtools/stack_dump.cpp b/emulator/qtools/stack_dump.cpp new file mode 100644 index 0000000..b5014ef --- /dev/null +++ b/emulator/qtools/stack_dump.cpp @@ -0,0 +1,105 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> +#include <assert.h> +#include "trace_reader.h" +#include "bitvector.h" +#include "parse_options.h" +#include "armdis.h" + +typedef TraceReader<> TraceReaderType; + +#include "parse_options-inl.h" +#include "callstack.h" + +class MyFrame : public StackFrame<symbol_type> { + public: + void push(int stackLevel, uint64_t time, CallStackBase *base); + void pop(int stackLevel, uint64_t time, CallStackBase *base); +}; + +typedef CallStack<MyFrame> CallStackType; + +void MyFrame::push(int stackLevel, uint64_t time, CallStackBase *base) +{ + printf("%llu en thr %d %3d", time, base->getId(), stackLevel); + for (int ii = 0; ii < stackLevel; ++ii) + printf("."); + printf(" 0x%08x %s\n", addr, function->name); +} + +void MyFrame::pop(int stackLevel, uint64_t time, CallStackBase *base) +{ + printf("%llu x thr %d %3d", time, base->getId(), stackLevel); + for (int ii = 0; ii < stackLevel; ++ii) + printf("."); + printf(" 0x%08x %s\n", addr, function->name); +} + +static const int kNumStackFrames = 500; +static const int kMaxThreads = (32 * 1024); +CallStackType *stacks[kMaxThreads]; + +static uint64_t debugTime; + +void Usage(const char *program) +{ + fprintf(stderr, "Usage: %s [options] trace_name elf_file\n", + program); + OptionsUsage(); +} + +int main(int argc, char **argv) +{ + ParseOptions(argc, argv); + if (argc - optind != 2) { + Usage(argv[0]); + exit(1); + } + + char *qemu_trace_file = argv[optind++]; + char *elf_file = argv[optind++]; + TraceReaderType *trace = new TraceReaderType; + trace->Open(qemu_trace_file); + trace->ReadKernelSymbols(elf_file); + trace->SetRoot(root); + TraceHeader *qheader = trace->GetHeader(); + uint64_t startTime = qheader->start_sec; + startTime = (startTime << 32) | qheader->start_usec; + + BBEvent event; + while (1) { + BBEvent ignored; + symbol_type *function; + + if (GetNextValidEvent(trace, &event, &ignored, &function)) + break; + if (event.bb_num == 0) + break; + + // Get the stack for the current thread + CallStackType *pStack = stacks[event.pid]; + + // If the stack does not exist, then allocate a new one. + if (pStack == NULL) { + pStack = new CallStackType(event.pid, kNumStackFrames, trace); + stacks[event.pid] = pStack; + } + if (debugTime != 0 && event.time >= debugTime) + printf("debug time: %lld\n", debugTime); + + // Update the stack + pStack->updateStack(&event, function); + } + + for (int ii = 0; ii < kMaxThreads; ++ii) { + if (stacks[ii]) + stacks[ii]->popAll(event.time); + } + + delete trace; + return 0; +} diff --git a/emulator/qtools/tests/common_head.mk b/emulator/qtools/tests/common_head.mk new file mode 100644 index 0000000..e8170e9 --- /dev/null +++ b/emulator/qtools/tests/common_head.mk @@ -0,0 +1,25 @@ +CC := arm-elf-gcc +LD := arm-elf-ld +AS := arm-elf-as +OBJCOPY := arm-elf-objcopy +OBJDUMP := arm-elf-objdump + +OPT := -g +CFLAGS := $(OPT) -mcpu=arm9 + +.SUFFIXES: .dis .bin .elf + +.c.elf: + $(CC) $(CFLAGS) -Xlinker --script ../tests.ld -o $@ $< -nostdlib + +.c.s: + $(CC) $(CFLAGS) -static -S $< + +.S.elf: + $(CC) $(CFLAGS) -Xlinker --script ../tests.ld -nostdlib -o $@ $< + +.elf.dis: + $(OBJDUMP) -adx $< > $@ + +.elf.bin: + $(OBJCOPY) -O binary $< $@ diff --git a/emulator/qtools/tests/common_tail.mk b/emulator/qtools/tests/common_tail.mk new file mode 100644 index 0000000..be1c141 --- /dev/null +++ b/emulator/qtools/tests/common_tail.mk @@ -0,0 +1,3 @@ + +clean: + rm -f *.elf *.dis *.bin *.o *~ a.out diff --git a/emulator/qtools/tests/gtrace/Makefile b/emulator/qtools/tests/gtrace/Makefile new file mode 100644 index 0000000..1d2050c --- /dev/null +++ b/emulator/qtools/tests/gtrace/Makefile @@ -0,0 +1,18 @@ +include ../common_head.mk + +P4ROOT=/work/android +QEMU=$(P4ROOT)/device/tools/qemu/arm-softmmu/qemu-system-arm +QTOOLS=$(P4ROOT)/device/tools/qtools + +all: test.elf test.bin test.dis + +trace: test.elf test.bin + $(QEMU) -QEMU -kernel test.bin -trace foo + $(QTOOLS)/post_trace foo + $(QTOOLS)/read_trace foo test.elf > t1 + +gtrace: trace + $(QTOOLS)/q2g -r $(P4ROOT)/device/out/linux-arm-release/symbols foo test.elf foo.gtrace + gtracepost64 foo.gtrace > t2 + +include ../common_tail.mk diff --git a/emulator/qtools/tests/gtrace/test.c b/emulator/qtools/tests/gtrace/test.c new file mode 100644 index 0000000..e56a0f1 --- /dev/null +++ b/emulator/qtools/tests/gtrace/test.c @@ -0,0 +1,201 @@ +#include "../macros.h" + +int foo1(); +int foo2(); +void bar(); +int child1(); +int child2(); +int child3(); +int child4(); +int child5(); + +int global; + +void start() +{ + // Set the stack pointer + asm(" mov r13,#0x200000"); + PRINT_STR("hello\n"); + TRACE_INIT_NAME(701, "proc_foo"); + TRACE_INIT_NAME(702, "proc_bar"); + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + TRACE_SWITCH(702); + if (global++ > 0) + global++; + bar(); + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo2(); + TRACE_SWITCH(703); + if (global++ > 0) + global++; + foo1(); + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(704); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(705); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(706); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(707); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(708); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(709); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(701); + if (global++ > 0) + global++; + foo1(); + + TRACE_SWITCH(710); + if (global++ > 0) + global++; + foo1(); + + TRACE_STOP_EMU(); +} + +int foo1() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 3; ++ii) { + a += child1(); + a += child2(); + a += child3(); + } + return a; +} + +int foo2() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 2; ++ii) { + a += child3(); + a += child4(); + a += child5(); + } + return a; +} + +#define kStride 64 +void bar() +{ + int a = 0; + + static char mem[1000 * kStride]; + + int ii, jj; + + for (ii = 0; ii < 4; ++ii) { + for (jj = 0; jj < 10; ++jj) + a += mem[jj * kStride]; + foo1(); + foo2(); + } +} + +int child1() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 2; ++ii) + a += ii; + return a; +} + +int child2() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 4; ++ii) + a += ii; + return a; +} + +int child3() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 6; ++ii) + a += ii; + return a; +} + +int child4() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 8; ++ii) + a += ii; + return a; +} + +int child5() +{ + int a = 0; + + int ii; + for (ii = 0; ii < 10; ++ii) + a += ii; + return a; +} diff --git a/emulator/qtools/tests/macros.h b/emulator/qtools/tests/macros.h new file mode 100644 index 0000000..066374b --- /dev/null +++ b/emulator/qtools/tests/macros.h @@ -0,0 +1,93 @@ +#ifndef _TEST_TRACE_C_H_ +#define _TEST_TRACE_C_H_ + +/* the base address of trace device */ +#define TRACE_DEV_BASE_ADDR 0x21000000 + +/*the register addresses of the trace device */ +#define TRACE_DEV_REG_SWITCH 0 +#define TRACE_DEV_REG_FORK 1 +#define TRACE_DEV_REG_EXECVE_PID 2 +#define TRACE_DEV_REG_EXECVE_VMSTART 3 +#define TRACE_DEV_REG_EXECVE_VMEND 4 +#define TRACE_DEV_REG_EXECVE_OFFSET 5 +#define TRACE_DEV_REG_EXECVE_EXEPATH 6 +#define TRACE_DEV_REG_EXIT 7 +#define TRACE_DEV_REG_CMDLINE 8 +#define TRACE_DEV_REG_CMDLINE_LEN 9 +#define TRACE_DEV_REG_MMAP_EXEPATH 10 +#define TRACE_DEV_REG_INIT_PID 11 +#define TRACE_DEV_REG_INIT_NAME 12 +#define TRACE_DEV_REG_CLONE 13 +#define TRACE_DEV_REG_DYN_SYM 50 +#define TRACE_DEV_REG_DYN_SYM_ADDR 51 +#define TRACE_DEV_REG_PRINT_STR 60 +#define TRACE_DEV_REG_PRINT_NUM_DEC 61 +#define TRACE_DEV_REG_PRINT_NUM_HEX 62 +#define TRACE_DEV_REG_STOP_EMU 90 +#define TRACE_DEV_REG_ENABLE 100 +#define TRACE_DEV_REG_DISABLE 101 + +/* write a word to a trace device register */ +#define DEV_WRITE_WORD(addr,value)\ + (*(volatile unsigned long *)(TRACE_DEV_BASE_ADDR + ((addr) << 2)) = (value)) + +/*************************************************************/ +/* generates test events */ + +/* context switch */ +#define TRACE_SWITCH(pid) DEV_WRITE_WORD(TRACE_DEV_REG_SWITCH, (pid)) +/* fork */ +#define TRACE_FORK(pid) DEV_WRITE_WORD(TRACE_DEV_REG_FORK, (pid)) +/* clone */ +#define TRACE_CLONE(pid) DEV_WRITE_WORD(TRACE_DEV_REG_CLONE, (pid)) +/* dump name and path of threads executed before trace device created */ +#define TRACE_INIT_NAME(pid,path)\ +do {\ + DEV_WRITE_WORD(TRACE_DEV_REG_INIT_PID, (pid));\ + DEV_WRITE_WORD(TRACE_DEV_REG_INIT_NAME, (unsigned long)(path));\ +}while(0) +/* dump exec mapping of threads executed before trace device created */ +#define TRACE_INIT_EXEC(vstart,vend,eoff,path)\ +do {\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_VMSTART, (vstart));\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_VMEND, (vend));\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_OFFSET, (eoff));\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_EXEPATH, (unsigned long)(path));\ +}while(0) +/* mmap */ +#define TRACE_MMAP(vstart,vend,eoff,path)\ +do {\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_VMSTART, (vstart));\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_VMEND, (vend));\ + DEV_WRITE_WORD(TRACE_DEV_REG_EXECVE_OFFSET, (eoff));\ + DEV_WRITE_WORD(TRACE_DEV_REG_MMAP_EXEPATH, (unsigned long)(path));\ +}while(0) +/* execve */ +#define TRACE_EXECVE(cmdlen,cmd)\ +do {\ + DEV_WRITE_WORD(TRACE_DEV_REG_CMDLINE_LEN, (cmdlen));\ + DEV_WRITE_WORD(TRACE_DEV_REG_CMDLINE, (unsigned long)(cmd));\ +}while(0) +/* exit */ +#define TRACE_EXIT(retv) DEV_WRITE_WORD(TRACE_DEV_REG_EXIT, (retv)) + +/* other commands */ + +/* stop emulation */ +#define TRACE_STOP_EMU() DEV_WRITE_WORD(TRACE_DEV_REG_STOP_EMU, 1) +/* enable/disable tracing */ +#define TRACE_ENABLE_TRACING() DEV_WRITE_WORD(TRACE_DEV_REG_ENABLE, 1) +#define TRACE_DISABLE_TRACING() DEV_WRITE_WORD(TRACE_DEV_REG_DISABLE, 1) +/* dynamic symbols */ +#define TRACE_DYN_SYM(addr,sym)\ +do {\ + DEV_WRITE_WORD(TRACE_DEV_REG_DYN_SYM_ADDR, (addr));\ + DEV_WRITE_WORD(TRACE_DEV_REG_DYN_SYM, (unsigned long)(sym));\ +}while(0) +/* prints */ +#define PRINT_STR(str) DEV_WRITE_WORD(TRACE_DEV_REG_PRINT_STR, (unsigned long)(str)) +#define PRINT_NUM_DEC(num) DEV_WRITE_WORD(TRACE_DEV_REG_PRINT_NUM_DEC, (num)) +#define PRINT_NUM_HEX(num) DEV_WRITE_WORD(TRACE_DEV_REG_PRINT_NUM_HEX, (num)) + +#endif diff --git a/emulator/qtools/tests/tests.ld b/emulator/qtools/tests/tests.ld new file mode 100644 index 0000000..05fe41b --- /dev/null +++ b/emulator/qtools/tests/tests.ld @@ -0,0 +1,10 @@ +SECTIONS { + TEXT_START = 0x00010000 ; + DATA_START = 0x00200000 ; + handlers 0x0 : { *(handlers) } + .text TEXT_START : { *(.text) } + .data DATA_START : { *(.data) } + .bss : { *(.bss) *(COMMON) } + p00300000 0x00300000 : { *(p00300000) } + p00400000 0x00400000 : { *(p00400000) } +} diff --git a/emulator/qtools/thumbdis.cpp b/emulator/qtools/thumbdis.cpp new file mode 100644 index 0000000..f4294dd --- /dev/null +++ b/emulator/qtools/thumbdis.cpp @@ -0,0 +1,503 @@ +/* Instruction printing code for the ARM + Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002 + Free Software Foundation, Inc. + Contributed by Richard Earnshaw (rwe@pegasus.esprit.ec.org) + Modification by James G. Smith (jsmith@cygnus.co.uk) + +This file is part of libopcodes. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Modified to fit into the qtools framework. The main differences are: + * + * - The disassembly function returns a string instead of writing it to a + * file stream. + * + * - All the references to the struct "disassemble_info" have been removed. + * + * - A set of enums for the thumb opcodes have been defined, along with a + * "decode()" function that maps a thumb instruction to an opcode enum. + * + * - Eliminated uses of the special characters ', `, and ? from the + * thumb_opcodes[] table so that we can easily specify separate opcodes + * for distinct instructions. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "opcode.h" + + +struct thumb_opcode +{ + unsigned short value, mask; /* recognise instruction if (op&mask)==value */ + Opcode opcode; + char * assembler; /* how to disassemble this instruction */ +}; + +/* format of the assembler string : + + %% % + %<bitfield>d print the bitfield in decimal + %<bitfield>x print the bitfield in hex + %<bitfield>X print the bitfield as 1 hex digit without leading "0x" + %<bitfield>r print as an ARM register + %<bitfield>f print a floating point constant if >7 else a + floating point register + %<code>y print a single precision VFP reg. + Codes: 0=>Sm, 1=>Sd, 2=>Sn, 3=>multi-list, 4=>Sm pair + %<code>z print a double precision VFP reg + Codes: 0=>Dm, 1=>Dd, 2=>Dn, 3=>multi-list + %c print condition code (always bits 28-31) + %P print floating point precision in arithmetic insn + %Q print floating point precision in ldf/stf insn + %R print floating point rounding mode + %<bitnum>'c print specified char iff bit is one + %<bitnum>`c print specified char iff bit is zero + %<bitnum>?ab print a if bit is one else print b + %p print 'p' iff bits 12-15 are 15 + %t print 't' iff bit 21 set and bit 24 clear + %o print operand2 (immediate or register + shift) + %a print address for ldr/str instruction + %s print address for ldr/str halfword/signextend instruction + %b print branch destination + %B print arm BLX(1) destination + %A print address for ldc/stc/ldf/stf instruction + %m print register mask for ldm/stm instruction + %C print the PSR sub type. + %F print the COUNT field of a LFM/SFM instruction. +Thumb specific format options: + %D print Thumb register (bits 0..2 as high number if bit 7 set) + %S print Thumb register (bits 3..5 as high number if bit 6 set) + %<bitfield>I print bitfield as a signed decimal + (top bit of range being the sign bit) + %M print Thumb register mask + %N print Thumb register mask (with LR) + %O print Thumb register mask (with PC) + %T print Thumb condition code (always bits 8-11) + %I print cirrus signed shift immediate: bits 0..3|4..6 + %<bitfield>B print Thumb branch destination (signed displacement) + %<bitfield>W print (bitfield * 4) as a decimal + %<bitfield>H print (bitfield * 2) as a decimal + %<bitfield>a print (bitfield * 4) as a pc-rel offset + decoded symbol +*/ + + +static struct thumb_opcode thumb_opcodes[] = +{ + /* Thumb instructions. */ + + /* ARM V5 ISA extends Thumb. */ + {0xbe00, 0xff00, OP_THUMB_BKPT, "bkpt\t%0-7x"}, + {0x4780, 0xff87, OP_THUMB_BLX, "blx\t%3-6r"}, /* note: 4 bit register number. */ + /* Format 5 instructions do not update the PSR. */ + {0x1C00, 0xFFC0, OP_THUMB_MOV, "mov\t%0-2r, %3-5r"}, + /* Format 4. */ + {0x4000, 0xFFC0, OP_THUMB_AND, "and\t%0-2r, %3-5r"}, + {0x4040, 0xFFC0, OP_THUMB_EOR, "eor\t%0-2r, %3-5r"}, + {0x4080, 0xFFC0, OP_THUMB_LSL, "lsl\t%0-2r, %3-5r"}, + {0x40C0, 0xFFC0, OP_THUMB_LSR, "lsr\t%0-2r, %3-5r"}, + {0x4100, 0xFFC0, OP_THUMB_ASR, "asr\t%0-2r, %3-5r"}, + {0x4140, 0xFFC0, OP_THUMB_ADC, "adc\t%0-2r, %3-5r"}, + {0x4180, 0xFFC0, OP_THUMB_SBC, "sbc\t%0-2r, %3-5r"}, + {0x41C0, 0xFFC0, OP_THUMB_ROR, "ror\t%0-2r, %3-5r"}, + {0x4200, 0xFFC0, OP_THUMB_TST, "tst\t%0-2r, %3-5r"}, + {0x4240, 0xFFC0, OP_THUMB_NEG, "neg\t%0-2r, %3-5r"}, + {0x4280, 0xFFC0, OP_THUMB_CMP, "cmp\t%0-2r, %3-5r"}, + {0x42C0, 0xFFC0, OP_THUMB_CMN, "cmn\t%0-2r, %3-5r"}, + {0x4300, 0xFFC0, OP_THUMB_ORR, "orr\t%0-2r, %3-5r"}, + {0x4340, 0xFFC0, OP_THUMB_MUL, "mul\t%0-2r, %3-5r"}, + {0x4380, 0xFFC0, OP_THUMB_BIC, "bic\t%0-2r, %3-5r"}, + {0x43C0, 0xFFC0, OP_THUMB_MVN, "mvn\t%0-2r, %3-5r"}, + /* format 13 */ + {0xB000, 0xFF80, OP_THUMB_ADD, "add\tsp, #%0-6W"}, + {0xB080, 0xFF80, OP_THUMB_SUB, "sub\tsp, #%0-6W"}, + /* format 5 */ + {0x4700, 0xFF80, OP_THUMB_BX, "bx\t%S"}, + {0x4400, 0xFF00, OP_THUMB_ADD, "add\t%D, %S"}, + {0x4500, 0xFF00, OP_THUMB_CMP, "cmp\t%D, %S"}, + {0x4600, 0xFF00, OP_THUMB_MOV, "mov\t%D, %S"}, + /* format 14 */ + {0xB400, 0xFE00, OP_THUMB_PUSH, "push\t%N"}, + {0xBC00, 0xFE00, OP_THUMB_POP, "pop\t%O"}, + /* format 2 */ + {0x1800, 0xFE00, OP_THUMB_ADD, "add\t%0-2r, %3-5r, %6-8r"}, + {0x1A00, 0xFE00, OP_THUMB_SUB, "sub\t%0-2r, %3-5r, %6-8r"}, + {0x1C00, 0xFE00, OP_THUMB_ADD, "add\t%0-2r, %3-5r, #%6-8d"}, + {0x1E00, 0xFE00, OP_THUMB_SUB, "sub\t%0-2r, %3-5r, #%6-8d"}, + /* format 8 */ + {0x5200, 0xFE00, OP_THUMB_STRH, "strh\t%0-2r, [%3-5r, %6-8r]"}, + {0x5A00, 0xFE00, OP_THUMB_LDRH, "ldrh\t%0-2r, [%3-5r, %6-8r]"}, + {0x5600, 0xFE00, OP_THUMB_LDRSB, "ldrsb\t%0-2r, [%3-5r, %6-8r]"}, + {0x5E00, 0xFE00, OP_THUMB_LDRSH, "ldrsh\t%0-2r, [%3-5r, %6-8r]"}, + /* format 7 */ + {0x5000, 0xFE00, OP_THUMB_STR, "str\t%0-2r, [%3-5r, %6-8r]"}, + {0x5400, 0xFE00, OP_THUMB_STRB, "strb\t%0-2r, [%3-5r, %6-8r]"}, + {0x5800, 0xFE00, OP_THUMB_LDR, "ldr\t%0-2r, [%3-5r, %6-8r]"}, + {0x5C00, 0xFE00, OP_THUMB_LDRB, "ldrb\t%0-2r, [%3-5r, %6-8r]"}, + /* format 1 */ + {0x0000, 0xF800, OP_THUMB_LSL, "lsl\t%0-2r, %3-5r, #%6-10d"}, + {0x0800, 0xF800, OP_THUMB_LSR, "lsr\t%0-2r, %3-5r, #%6-10d"}, + {0x1000, 0xF800, OP_THUMB_ASR, "asr\t%0-2r, %3-5r, #%6-10d"}, + /* format 3 */ + {0x2000, 0xF800, OP_THUMB_MOV, "mov\t%8-10r, #%0-7d"}, + {0x2800, 0xF800, OP_THUMB_CMP, "cmp\t%8-10r, #%0-7d"}, + {0x3000, 0xF800, OP_THUMB_ADD, "add\t%8-10r, #%0-7d"}, + {0x3800, 0xF800, OP_THUMB_SUB, "sub\t%8-10r, #%0-7d"}, + /* format 6 */ + /* TODO: Disassemble PC relative "LDR rD,=<symbolic>" */ + {0x4800, 0xF800, OP_THUMB_LDR, "ldr\t%8-10r, [pc, #%0-7W]\t(%0-7a)"}, + /* format 9 */ + {0x6000, 0xF800, OP_THUMB_STR, "str\t%0-2r, [%3-5r, #%6-10W]"}, + {0x6800, 0xF800, OP_THUMB_LDR, "ldr\t%0-2r, [%3-5r, #%6-10W]"}, + {0x7000, 0xF800, OP_THUMB_STRB, "strb\t%0-2r, [%3-5r, #%6-10d]"}, + {0x7800, 0xF800, OP_THUMB_LDRB, "ldrb\t%0-2r, [%3-5r, #%6-10d]"}, + /* format 10 */ + {0x8000, 0xF800, OP_THUMB_STRH, "strh\t%0-2r, [%3-5r, #%6-10H]"}, + {0x8800, 0xF800, OP_THUMB_LDRH, "ldrh\t%0-2r, [%3-5r, #%6-10H]"}, + /* format 11 */ + {0x9000, 0xF800, OP_THUMB_STR, "str\t%8-10r, [sp, #%0-7W]"}, + {0x9800, 0xF800, OP_THUMB_LDR, "ldr\t%8-10r, [sp, #%0-7W]"}, + /* format 12 */ + {0xA000, 0xF800, OP_THUMB_ADD, "add\t%8-10r, pc, #%0-7W\t(adr %8-10r,%0-7a)"}, + {0xA800, 0xF800, OP_THUMB_ADD, "add\t%8-10r, sp, #%0-7W"}, + /* format 15 */ + {0xC000, 0xF800, OP_THUMB_STMIA, "stmia\t%8-10r!,%M"}, + {0xC800, 0xF800, OP_THUMB_LDMIA, "ldmia\t%8-10r!,%M"}, + /* format 18 */ + {0xE000, 0xF800, OP_THUMB_B, "b\t%0-10B"}, + /* format 19 */ + /* special processing required in disassembler */ + {0xF000, 0xF800, OP_THUMB_BL, ""}, + {0xF800, 0xF800, OP_THUMB_BL, "second half of BL instruction %0-15x"}, + {0xE800, 0xF800, OP_THUMB_BLX, "second half of BLX instruction %0-15x"}, + /* format 16 */ + {0xD000, 0xFF00, OP_THUMB_B, "beq\t%0-7B"}, + {0xD100, 0xFF00, OP_THUMB_B, "bne\t%0-7B"}, + {0xD200, 0xFF00, OP_THUMB_B, "bcs\t%0-7B"}, + {0xD300, 0xFF00, OP_THUMB_B, "bcc\t%0-7B"}, + {0xD400, 0xFF00, OP_THUMB_B, "bmi\t%0-7B"}, + {0xD500, 0xFF00, OP_THUMB_B, "bpl\t%0-7B"}, + {0xD600, 0xFF00, OP_THUMB_B, "bvs\t%0-7B"}, + {0xD700, 0xFF00, OP_THUMB_B, "bvc\t%0-7B"}, + {0xD800, 0xFF00, OP_THUMB_B, "bhi\t%0-7B"}, + {0xD900, 0xFF00, OP_THUMB_B, "bls\t%0-7B"}, + {0xDA00, 0xFF00, OP_THUMB_B, "bge\t%0-7B"}, + {0xDB00, 0xFF00, OP_THUMB_B, "blt\t%0-7B"}, + {0xDC00, 0xFF00, OP_THUMB_B, "bgt\t%0-7B"}, + {0xDD00, 0xFF00, OP_THUMB_B, "ble\t%0-7B"}, + /* format 17 */ + {0xDE00, 0xFF00, OP_THUMB_UNDEFINED, "undefined"}, + {0xDF00, 0xFF00, OP_THUMB_SWI, "swi\t%0-7d"}, + /* format 9 */ + {0x6000, 0xF800, OP_THUMB_STR, "str\t%0-2r, [%3-5r, #%6-10W]"}, + {0x6800, 0xF800, OP_THUMB_LDR, "ldr\t%0-2r, [%3-5r, #%6-10W]"}, + {0x7000, 0xF800, OP_THUMB_STRB, "strb\t%0-2r, [%3-5r, #%6-10d]"}, + {0x7800, 0xF800, OP_THUMB_LDRB, "ldrb\t%0-2r, [%3-5r, #%6-10d]"}, + /* the rest */ + {0x0000, 0x0000, OP_THUMB_UNDEFINED, "undefined instruction %0-15x"}, + {0x0000, 0x0000, OP_END, 0} +}; + +#define BDISP23(x,y) ((((((x) & 0x07ff) << 11) | ((y) & 0x07ff)) \ + ^ 0x200000) - 0x200000) /* 23bit */ + +static char * arm_conditional[] = +{"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "", "nv"}; + +typedef struct +{ + const char * name; + const char * description; + const char * reg_names[16]; +} +arm_regname; + +static arm_regname regnames[] = +{ + { "raw" , "Select raw register names", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}}, + { "gcc", "Select register names used by GCC", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "sl", "fp", "ip", "sp", "lr", "pc" }}, + { "std", "Select register names used in ARM's ISA documentation", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc" }}, + { "apcs", "Select register names used in the APCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "sl", "fp", "ip", "sp", "lr", "pc" }}, + { "atpcs", "Select register names used in the ATPCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "IP", "SP", "LR", "PC" }}, + { "special-atpcs", "Select special register names used in the ATPCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "WR", "v5", "SB", "SL", "FP", "IP", "SP", "LR", "PC" }} +}; + +/* Default to STD register name set. */ +static unsigned int regname_selected = 2; + +#define NUM_ARM_REGNAMES NUM_ELEM (regnames) +#define arm_regnames regnames[regname_selected].reg_names + +Opcode decode_insn_thumb(uint32_t given) +{ + struct thumb_opcode * insn; + + for (insn = thumb_opcodes; insn->assembler; insn++) { + if ((given & insn->mask) == insn->value) + return insn->opcode; + } + return OP_THUMB_UNDEFINED; +} + +// Generates the disassembly string for the thumb instruction "insn1". +// If "insn1" is a BL or BLX instruction that is the first of two Thumb +// instructions, then insn2 is the second of two instructions. Otherwise, +// insn2 is ignored. +char *disasm_insn_thumb(uint32_t pc, uint32_t insn1, uint32_t insn2, char *result) +{ + struct thumb_opcode * insn; + static char buf[80]; + char *ptr; + uint32_t addr; + int len; + + if (result == NULL) + result = buf; + ptr = result; + + for (insn = thumb_opcodes; insn->assembler; insn++) { + if ((insn1 & insn->mask) != insn->value) + continue; + + char * c = insn->assembler; + + /* Special processing for Thumb 2-instruction BL sequence: */ + if (!*c) { /* Check for empty (not NULL) assembler string. */ + uint32_t offset; + + offset = BDISP23 (insn1, insn2); + offset = offset * 2 + pc + 4; + + if ((insn2 & 0x1000) == 0) { + len = sprintf(ptr, "blx\t"); + offset &= 0xfffffffc; + } else { + len = sprintf(ptr, "bl\t"); + } + ptr += len; + + sprintf(ptr, "0x%x", offset); + return result; + } + + insn1 &= 0xffff; + + for (; *c; c++) { + if (*c != '%') { + len = sprintf(ptr, "%c", *c); + ptr += len; + continue; + } + + int domaskpc = 0; + int domasklr = 0; + + switch (*++c) { + case '%': + len = sprintf(ptr, "%%"); + ptr += len; + break; + + case 'S': { + uint32_t reg; + + reg = (insn1 >> 3) & 0x7; + if (insn1 & (1 << 6)) + reg += 8; + + len = sprintf(ptr, "%s", arm_regnames[reg]); + ptr += len; + break; + } + + case 'D': { + uint32_t reg; + + reg = insn1 & 0x7; + if (insn1 & (1 << 7)) + reg += 8; + + len = sprintf(ptr, "%s", arm_regnames[reg]); + ptr += len; + break; + } + + case 'T': + len = sprintf(ptr, "%s", + arm_conditional [(insn1 >> 8) & 0xf]); + ptr += len; + break; + + case 'N': + if (insn1 & (1 << 8)) + domasklr = 1; + /* Fall through. */ + case 'O': + if (*c == 'O' && (insn1 & (1 << 8))) + domaskpc = 1; + /* Fall through. */ + case 'M': { + int started = 0; + int reg; + + len = sprintf(ptr, "{"); + ptr += len; + + /* It would be nice if we could spot + ranges, and generate the rS-rE format: */ + for (reg = 0; (reg < 8); reg++) + if ((insn1 & (1 << reg)) != 0) { + if (started) { + len = sprintf(ptr, ", "); + ptr += len; + } + started = 1; + len = sprintf(ptr, "%s", arm_regnames[reg]); + ptr += len; + } + + if (domasklr) { + if (started) { + len = sprintf(ptr, ", "); + ptr += len; + } + started = 1; + len = sprintf(ptr, arm_regnames[14] /* "lr" */); + ptr += len; + } + + if (domaskpc) { + if (started) { + len = sprintf(ptr, ", "); + ptr += len; + } + len = sprintf(ptr, arm_regnames[15] /* "pc" */); + ptr += len; + } + + len = sprintf(ptr, "}"); + ptr += len; + break; + } + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': { + int bitstart = *c++ - '0'; + int bitend = 0; + + while (*c >= '0' && *c <= '9') + bitstart = (bitstart * 10) + *c++ - '0'; + + switch (*c) { + case '-': { + uint32_t reg; + + c++; + while (*c >= '0' && *c <= '9') + bitend = (bitend * 10) + *c++ - '0'; + if (!bitend) + abort (); + reg = insn1 >> bitstart; + reg &= (2 << (bitend - bitstart)) - 1; + switch (*c) { + case 'r': + len = sprintf(ptr, "%s", arm_regnames[reg]); + break; + + case 'd': + len = sprintf(ptr, "%d", reg); + break; + + case 'H': + len = sprintf(ptr, "%d", reg << 1); + break; + + case 'W': + len = sprintf(ptr, "%d", reg << 2); + break; + + case 'a': + /* PC-relative address -- the bottom two + bits of the address are dropped + before the calculation. */ + addr = ((pc + 4) & ~3) + (reg << 2); + len = sprintf(ptr, "0x%x", addr); + break; + + case 'x': + len = sprintf(ptr, "0x%04x", reg); + break; + + case 'I': + reg = ((reg ^ (1 << bitend)) - (1 << bitend)); + len = sprintf(ptr, "%d", reg); + break; + + case 'B': + reg = ((reg ^ (1 << bitend)) - (1 << bitend)); + addr = reg * 2 + pc + 4; + len = sprintf(ptr, "0x%x", addr); + break; + + default: + abort (); + } + ptr += len; + break; + } + + case '\'': + c++; + if ((insn1 & (1 << bitstart)) != 0) { + len = sprintf(ptr, "%c", *c); + ptr += len; + } + break; + + case '?': + ++c; + if ((insn1 & (1 << bitstart)) != 0) + len = sprintf(ptr, "%c", *c++); + else + len = sprintf(ptr, "%c", *++c); + ptr += len; + break; + + default: + abort (); + } + break; + } + + default: + abort (); + } + } + return result; + } + + /* No match. */ + abort (); +} diff --git a/emulator/qtools/trace_reader.cpp b/emulator/qtools/trace_reader.cpp new file mode 100644 index 0000000..b38c0b4 --- /dev/null +++ b/emulator/qtools/trace_reader.cpp @@ -0,0 +1,1201 @@ +// Copyright 2006 The Android Open Source Project + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <inttypes.h> +#include <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <elf.h> +#include "trace_reader.h" +#include "decoder.h" + +// A struct for creating temporary linked-lists of DexSym structs +struct DexSymList { + DexSymList *next; + DexSym sym; +}; + +// Declare static functions used in this file +static char *ExtractDexPathFromMmap(const char *mmap_path); +static void CopyDexSymbolsToArray(DexFileList *dexfile, + DexSymList *head, int num_symbols); + +// This function creates the pathname to the a specific trace file. The +// string space is allocated in this routine and must be freed by the +// caller. +static char *CreateTracePath(const char *filename, const char *ext) +{ + char *fname; + const char *base_start, *base_end; + int ii, len, base_len, dir_len, path_len, qtrace_len; + + // Handle error cases + if (filename == NULL || *filename == 0 || strcmp(filename, "/") == 0) + return NULL; + + // Ignore a trailing slash, if any + len = strlen(filename); + if (filename[len - 1] == '/') + len -= 1; + + // Find the basename. We don't use basename(3) because there are + // different behaviors for GNU and Posix in the case where the + // last character is a slash. + base_start = base_end = &filename[len]; + for (ii = 0; ii < len; ++ii) { + base_start -= 1; + if (*base_start == '/') { + base_start += 1; + break; + } + } + base_len = base_end - base_start; + dir_len = len - base_len; + qtrace_len = strlen("/qtrace"); + + // Create space for the pathname: "/dir/basename/qtrace.ext" + // The "ext" string already contains the dot, so just add a byte + // for the terminating zero. + path_len = dir_len + base_len + qtrace_len + strlen(ext) + 1; + fname = new char[path_len]; + if (dir_len > 0) + strncpy(fname, filename, dir_len); + fname[dir_len] = 0; + strncat(fname, base_start, base_len); + strcat(fname, "/qtrace"); + strcat(fname, ext); + return fname; +} + +inline BBReader::Future *BBReader::AllocFuture() +{ + Future *future = free_; + free_ = free_->next; + return future; +} + +inline void BBReader::FreeFuture(Future *future) +{ + future->next = free_; + free_ = future; +} + +inline void BBReader::InsertFuture(Future *future) +{ + uint64_t future_time = future->bb.next_time; + Future *prev = NULL; + Future *ptr; + for (ptr = head_; ptr; prev = ptr, ptr = ptr->next) { + if (future_time <= ptr->bb.next_time) + break; + } + if (prev == NULL) { + // link it at the front + future->next = head_; + head_ = future; + } else { + // link it after "prev" + future->next = prev->next; + prev->next = future; + } +} + +// Decodes the next basic block record from the file. Returns 1 +// at end-of-file, otherwise returns 0. +inline int BBReader::DecodeNextRec() +{ + int64_t bb_diff = decoder_->Decode(true); + uint64_t time_diff = decoder_->Decode(false); + nextrec_.bb_rec.repeat = decoder_->Decode(false); + if (time_diff == 0) + return 1; + if (nextrec_.bb_rec.repeat) + nextrec_.bb_rec.time_diff = decoder_->Decode(false); + nextrec_.bb_rec.bb_num += bb_diff; + nextrec_.bb_rec.start_time += time_diff; + return 0; +} + +BBReader::BBReader(TraceReaderBase *trace) +{ + trace_ = trace; + decoder_ = new Decoder; +} + +BBReader::~BBReader() +{ + delete decoder_; +} + +void BBReader::Open(char *filename) +{ + // Initialize the class variables + memset(&nextrec_, 0, sizeof(TimeRec)); + memset(futures_, 0, sizeof(Future) * kMaxNumBasicBlocks); + head_ = NULL; + + // Link all of the futures_[] array elements on the free list. + for (int ii = 0; ii < kMaxNumBasicBlocks - 1; ++ii) { + futures_[ii].next = &futures_[ii + 1]; + } + futures_[kMaxNumBasicBlocks - 1].next = 0; + free_ = &futures_[0]; + + // Open the trace.bb file + char *fname = CreateTracePath(filename, ".bb"); + decoder_->Open(fname); + is_eof_ = DecodeNextRec(); + delete[] fname; +} + +void BBReader::Close() +{ + decoder_->Close(); +} + +// Returns true at end of file. +bool BBReader::ReadBB(BBEvent *event) +{ + if (is_eof_ && head_ == NULL) { + return true; + } + +#if 0 + if (nextrec_) { + printf("nextrec: buffer[%d], bb_num: %lld start: %d diff %d repeat %d next %u\n", + nextrec_ - &buffer_[0], + nextrec_->bb_rec.bb_num, nextrec_->bb_rec.start_time, + nextrec_->bb_rec.time_diff, nextrec_->bb_rec.repeat, + nextrec_->next_time); + } + if (head_) { + printf("head: 0x%x, bb_num: %lld start: %d diff %d repeat %d next %u\n", + head_, + head_->bb->bb_rec.bb_num, head_->bb->bb_rec.start_time, + head_->bb->bb_rec.time_diff, head_->bb->bb_rec.repeat, + head_->bb->next_time); + } +#endif + if (!is_eof_) { + if (head_) { + TimeRec *bb = &head_->bb; + if (bb->next_time < nextrec_.bb_rec.start_time) { + // The head is earlier. + event->time = bb->next_time; + event->bb_num = bb->bb_rec.bb_num; + event->bb_addr = trace_->GetBBAddr(event->bb_num); + event->insns = trace_->GetInsns(event->bb_num); + event->num_insns = trace_->FindNumInsns(event->bb_num, event->time); + event->pid = trace_->FindCurrentPid(event->time); + event->is_thumb = trace_->GetIsThumb(event->bb_num); + + // Remove the head element from the list + Future *future = head_; + head_ = head_->next; + if (bb->bb_rec.repeat > 0) { + // there are more repetitions of this bb + bb->bb_rec.repeat -= 1; + bb->next_time += bb->bb_rec.time_diff; + + // Insert this future into the sorted list + InsertFuture(future); + } else { + // Add this future to the free list + FreeFuture(future); + } + return false; + } + } + // The nextrec is earlier (or there was no head) + event->time = nextrec_.bb_rec.start_time; + event->bb_num = nextrec_.bb_rec.bb_num; + event->bb_addr = trace_->GetBBAddr(event->bb_num); + event->insns = trace_->GetInsns(event->bb_num); + event->num_insns = trace_->FindNumInsns(event->bb_num, event->time); + event->pid = trace_->FindCurrentPid(event->time); + event->is_thumb = trace_->GetIsThumb(event->bb_num); + if (nextrec_.bb_rec.repeat > 0) { + Future *future = AllocFuture(); + future->bb.bb_rec = nextrec_.bb_rec; + future->bb.bb_rec.repeat -= 1; + future->bb.next_time = nextrec_.bb_rec.start_time + nextrec_.bb_rec.time_diff; + InsertFuture(future); + } + + is_eof_ = DecodeNextRec(); + return false; + } + + //printf("using head_ 0x%x\n", head_); + assert(head_); + TimeRec *bb = &head_->bb; + event->time = bb->next_time; + event->bb_num = bb->bb_rec.bb_num; + event->bb_addr = trace_->GetBBAddr(event->bb_num); + event->insns = trace_->GetInsns(event->bb_num); + event->num_insns = trace_->FindNumInsns(event->bb_num, event->time); + event->pid = trace_->FindCurrentPid(event->time); + event->is_thumb = trace_->GetIsThumb(event->bb_num); + + // Remove the head element from the list + Future *future = head_; + head_ = head_->next; + if (bb->bb_rec.repeat > 0) { + // there are more repetitions of this bb + bb->bb_rec.repeat -= 1; + bb->next_time += bb->bb_rec.time_diff; + + // Insert this future into the sorted list + InsertFuture(future); + } else { + // Add this future to the free list + FreeFuture(future); + } + return false; +} + +InsnReader::InsnReader() +{ + decoder_ = new Decoder; +} + +InsnReader::~InsnReader() +{ + delete decoder_; +} + +void InsnReader::Open(char *filename) +{ + prev_time_ = 0; + time_diff_ = 0; + repeat_ = -1; + + // Open the trace.insn file + char *fname = CreateTracePath(filename, ".insn"); + decoder_->Open(fname); + delete[] fname; +} + +void InsnReader::Close() +{ + decoder_->Close(); +} + +uint64_t InsnReader::ReadInsnTime(uint64_t min_time) +{ + do { + if (repeat_ == -1) { + time_diff_ = decoder_->Decode(false); + repeat_ = decoder_->Decode(false); + } + prev_time_ += time_diff_; + repeat_ -= 1; + } while (prev_time_ < min_time); + return prev_time_; +} + +AddrReader::AddrReader() +{ + decoder_ = new Decoder; + opened_ = false; +} + +AddrReader::~AddrReader() +{ + delete decoder_; +} + +// Returns true if there is an error opening the file +bool AddrReader::Open(char *filename, char *suffix) +{ + struct stat stat_buf; + + prev_addr_ = 0; + prev_time_ = 0; + + // Open the trace.addr file + char *fname = CreateTracePath(filename, suffix); + int rval = stat(fname, &stat_buf); + if (rval == -1) { + // The file does not exist + delete[] fname; + return true; + } + decoder_->Open(fname); + opened_ = true; + delete[] fname; + return false; +} + +void AddrReader::Close() +{ + decoder_->Close(); +} + +// Returns true at end of file. +bool AddrReader::ReadAddr(uint64_t *time, uint32_t *addr) +{ + if (!opened_) { + fprintf(stderr, "Cannot read address trace\n"); + exit(1); + } + uint32_t addr_diff = decoder_->Decode(true); + uint64_t time_diff = decoder_->Decode(false); + if (time_diff == 0 && addr_diff == 0) { + *addr = 0; + *time = 0; + return true; + } + prev_addr_ += addr_diff; + prev_time_ += time_diff; + *addr = prev_addr_; + *time = prev_time_; + return false; +} + +ExcReader::ExcReader() +{ + decoder_ = new Decoder; +} + +ExcReader::~ExcReader() +{ + delete decoder_; +} + +void ExcReader::Open(char *filename) +{ + prev_time_ = 0; + prev_recnum_ = 0; + + // Open the trace.exc file + char *fname = CreateTracePath(filename, ".exc"); + decoder_->Open(fname); + delete[] fname; +} + +void ExcReader::Close() +{ + decoder_->Close(); +} + +// Returns true at end of file. +bool ExcReader::ReadExc(uint64_t *time, uint32_t *current_pc, uint64_t *recnum, + uint32_t *target_pc, uint64_t *bb_num, + uint64_t *bb_start_time, int *num_insns) +{ + uint64_t time_diff = decoder_->Decode(false); + uint32_t pc = decoder_->Decode(false); + if ((time_diff | pc) == 0) { + decoder_->Decode(false); + decoder_->Decode(false); + decoder_->Decode(false); + decoder_->Decode(false); + decoder_->Decode(false); + return true; + } + uint64_t recnum_diff = decoder_->Decode(false); + prev_time_ += time_diff; + prev_recnum_ += recnum_diff; + *time = prev_time_; + *current_pc = pc; + *recnum = prev_recnum_; + *target_pc = decoder_->Decode(false); + *bb_num = decoder_->Decode(false); + *bb_start_time = decoder_->Decode(false); + *num_insns = decoder_->Decode(false); + return false; +} + +PidReader::PidReader() +{ + decoder_ = new Decoder; +} + +PidReader::~PidReader() +{ + delete decoder_; +} + +void PidReader::Open(char *filename) +{ + prev_time_ = 0; + + // Open the trace.pid file + char *fname = CreateTracePath(filename, ".pid"); + decoder_->Open(fname); + delete[] fname; +} + +void PidReader::Close() +{ + decoder_->Close(); +} + +// Returns true at end of file. +bool PidReader::ReadPidEvent(PidEvent *event) +{ + uint64_t time_diff = decoder_->Decode(false); + int rec_type = decoder_->Decode(false); + prev_time_ += time_diff; + event->time = prev_time_; + event->rec_type = rec_type; + switch(rec_type) { + case kPidEndOfFile: + return true; + case kPidSwitch: + case kPidExit: + event->pid = decoder_->Decode(false); + break; + case kPidFork: + case kPidClone: + event->tgid = decoder_->Decode(false); + event->pid = decoder_->Decode(false); + break; + case kPidMmap: + { + event->vstart = decoder_->Decode(false); + event->vend = decoder_->Decode(false); + event->offset = decoder_->Decode(false); + int len = decoder_->Decode(false); + char *path = new char[len + 1]; + decoder_->Read(path, len); + path[len] = 0; + event->path = path; + event->mmap_path = path; + char *dexfile = ExtractDexPathFromMmap(path); + if (dexfile != NULL) { + delete[] event->path; + event->path = dexfile; + } + } + break; + case kPidMunmap: + { + event->vstart = decoder_->Decode(false); + event->vend = decoder_->Decode(false); + } + break; + case kPidSymbolAdd: + { + event->vstart = decoder_->Decode(false); + int len = decoder_->Decode(false); + char *path = new char[len + 1]; + decoder_->Read(path, len); + path[len] = 0; + event->path = path; + } + break; + case kPidSymbolRemove: + event->vstart = decoder_->Decode(false); + break; + case kPidExec: + { + int argc = decoder_->Decode(false); + event->argc = argc; + char **argv = new char*[argc]; + event->argv = argv; + for (int ii = 0; ii < argc; ++ii) { + int alen = decoder_->Decode(false); + argv[ii] = new char[alen + 1]; + decoder_->Read(argv[ii], alen); + argv[ii][alen] = 0; + } + } + break; + case kPidName: + case kPidKthreadName: + { + if (rec_type == kPidKthreadName) { + event->tgid = decoder_->Decode(false); + } + event->pid = decoder_->Decode(false); + int len = decoder_->Decode(false); + char *path = new char[len + 1]; + decoder_->Read(path, len); + path[len] = 0; + event->path = path; + } + break; + } + return false; +} + +// Frees the memory that might have been allocated for the given event. +void PidReader::Dispose(PidEvent *event) +{ + switch(event->rec_type) { + case kPidMmap: + case kPidSymbolAdd: + case kPidName: + case kPidKthreadName: + delete[] event->path; + event->path = NULL; + event->mmap_path = NULL; + break; + + case kPidExec: + for (int ii = 0; ii < event->argc; ++ii) { + delete[] event->argv[ii]; + } + delete[] event->argv; + event->argv = NULL; + event->argc = 0; + break; + } +} + + +MethodReader::MethodReader() +{ + decoder_ = new Decoder; + opened_ = false; +} + +MethodReader::~MethodReader() +{ + delete decoder_; +} + +bool MethodReader::Open(char *filename) +{ + struct stat stat_buf; + + prev_time_ = 0; + prev_addr_ = 0; + prev_pid_ = 0; + + // Open the trace.method file + char *fname = CreateTracePath(filename, ".method"); + int rval = stat(fname, &stat_buf); + if (rval == -1) { + // The file does not exist + delete[] fname; + return true; + } + decoder_->Open(fname); + delete[] fname; + opened_ = true; + return false; +} + +void MethodReader::Close() +{ + decoder_->Close(); +} + +// Returns true at end of file. +bool MethodReader::ReadMethod(MethodRec *method_record) +{ + if (!opened_) + return true; + uint64_t time_diff = decoder_->Decode(false); + int32_t addr_diff = decoder_->Decode(true); + if (time_diff == 0) { + method_record->time = 0; + method_record->addr = 0; + method_record->flags = 0; + return true; + } + int32_t pid_diff = decoder_->Decode(true); + prev_time_ += time_diff; + prev_addr_ += addr_diff; + prev_pid_ += pid_diff; + method_record->time = prev_time_; + method_record->addr = prev_addr_; + method_record->pid = prev_pid_; + method_record->flags = decoder_->Decode(false); + return false; +} + +TraceReaderBase::TraceReaderBase() +{ + static_filename_ = NULL; + static_fstream_ = NULL; + header_ = new TraceHeader; + bb_reader_ = new BBReader(this); + insn_reader_ = new InsnReader; + load_addr_reader_ = new AddrReader; + store_addr_reader_ = new AddrReader; + exc_reader_ = new ExcReader; + pid_reader_ = new PidReader; + method_reader_ = new MethodReader; + internal_exc_reader_ = new ExcReader; + internal_pid_reader_ = new PidReader; + internal_method_reader_ = new MethodReader; + blocks_ = NULL; + bb_recnum_ = 0; + exc_recnum_ = 0; + exc_end_ = false; + exc_bb_num_ = 0; + exc_time_ = 0; + exc_num_insns_ = 0; + current_pid_ = 0; + next_pid_ = 0; + next_pid_switch_time_ = 0; + post_processing_ = false; + dex_hash_ = NULL; + load_eof_ = false; + load_time_ = 0; + load_addr_ = 0; + store_eof_ = false; + store_time_ = 0; + store_addr_ = 0; +} + +TraceReaderBase::~TraceReaderBase() +{ + Close(); + delete bb_reader_; + delete insn_reader_; + delete load_addr_reader_; + delete store_addr_reader_; + delete exc_reader_; + delete pid_reader_; + delete method_reader_; + delete internal_exc_reader_; + delete internal_pid_reader_; + delete internal_method_reader_; + if (blocks_) { + int num_static_bb = header_->num_static_bb; + for (int ii = 0; ii < num_static_bb; ++ii) { + delete[] blocks_[ii].insns; + } + delete[] blocks_; + } + delete header_; + if (dex_hash_ != NULL) { + HashTable<DexFileList*>::entry_type *ptr; + for (ptr = dex_hash_->GetFirst(); ptr; ptr = dex_hash_->GetNext()) { + DexFileList *dexfile = ptr->value; + delete[] dexfile->path; + int nsymbols = dexfile->nsymbols; + DexSym *symbols = dexfile->symbols; + for (int ii = 0; ii < nsymbols; ii++) { + delete[] symbols[ii].name; + } + delete[] dexfile->symbols; + delete dexfile; + } + } + delete dex_hash_; + delete[] static_filename_; +} + +void TraceReaderBase::ReadTraceHeader(FILE *fstream, char *filename, + char *tracename, TraceHeader *header) +{ + int rval = fread(header, sizeof(TraceHeader), 1, fstream); + if (rval != 1) { + perror(filename); + exit(1); + } + + if (!post_processing_ && strcmp(header->ident, TRACE_IDENT) != 0) { + fprintf(stderr, "%s: missing trace header; run 'post_trace %s' first\n", + filename, tracename); + exit(1); + } + + if (header->version != TRACE_VERSION) { + fprintf(stderr, + "%s: trace header version (%d) does not match compiled tools version (%d)\n", + tracename, header->version, TRACE_VERSION); + exit(1); + } + + convert32(header->version); + convert32(header->start_sec); + convert32(header->start_usec); + convert32(header->pdate); + convert32(header->ptime); + convert64(header->num_static_bb); + convert64(header->num_static_insn); + convert64(header->num_dynamic_bb); + convert64(header->num_dynamic_insn); + convert64(header->elapsed_usecs); +} + + +void TraceReaderBase::Open(char *filename) +{ + char *fname; + FILE *fstream; + + // Open the qtrace.bb file + bb_reader_->Open(filename); + + // Open the qtrace.insn file + insn_reader_->Open(filename); + + // Open the qtrace.load file and read the first line + load_eof_ = load_addr_reader_->Open(filename, ".load"); + if (!load_eof_) + load_eof_ = load_addr_reader_->ReadAddr(&load_time_, &load_addr_); + + // Open the qtrace.store file and read the first line + store_eof_ = store_addr_reader_->Open(filename, ".store"); + if (!store_eof_) + store_eof_ = store_addr_reader_->ReadAddr(&store_time_, &store_addr_); + + // Open the qtrace.exc file + exc_reader_->Open(filename); + + // Open another file stream to the qtrace.exc file for internal reads. + // This allows the caller to also read from the qtrace.exc file. + internal_exc_reader_->Open(filename); + + // Open the qtrace.pid file + pid_reader_->Open(filename); + internal_pid_reader_->Open(filename); + + // Open the qtrace.method file + method_reader_->Open(filename); + internal_method_reader_->Open(filename); + + // Open the qtrace.static file + fname = CreateTracePath(filename, ".static"); + static_filename_ = fname; + + fstream = fopen(fname, "r"); + if (fstream == NULL) { + perror(fname); + exit(1); + } + static_fstream_ = fstream; + + // Read the header + ReadTraceHeader(fstream, fname, filename, header_); + + // Allocate space for all of the static blocks + int num_static_bb = header_->num_static_bb; + if (num_static_bb) { + blocks_ = new StaticBlock[num_static_bb]; + + // Read in all the static blocks + for (int ii = 0; ii < num_static_bb; ++ii) { + ReadStatic(&blocks_[ii].rec); + int num_insns = blocks_[ii].rec.num_insns; + if (num_insns > 0) { + blocks_[ii].insns = new uint32_t[num_insns]; + ReadStaticInsns(num_insns, blocks_[ii].insns); + } else { + blocks_[ii].insns = NULL; + } + } + fseek(static_fstream_, sizeof(TraceHeader), SEEK_SET); + } + + ParseDexList(filename); + + // If the dex_hash_ is NULL, then assign it a small hash table + // so that we can simply do a Find() operation without having + // to check for NULL first. + if (dex_hash_ == NULL) { + dex_hash_ = new HashTable<DexFileList*>(1, NULL); + } +} + +// Reads the list of pid events looking for an mmap of a dex file. +PidEvent * TraceReaderBase::FindMmapDexFileEvent() +{ + static PidEvent event; + + while (!pid_reader_->ReadPidEvent(&event)) { + if (event.rec_type == kPidMmap && event.path != event.mmap_path) { + return &event; + } + pid_reader_->Dispose(&event); + } + return NULL; +} + +static void CopyDexSymbolsToArray(DexFileList *dexfile, + DexSymList *head, int num_symbols) +{ + if (dexfile == NULL) + return; + + DexSym *symbols = NULL; + if (num_symbols > 0) { + symbols = new DexSym[num_symbols]; + } + dexfile->nsymbols = num_symbols; + dexfile->symbols = symbols; + + // Copy the linked-list to the array. + DexSymList *next_sym = NULL; + int next_index = 0; + for (DexSymList *sym = head; sym; sym = next_sym) { + next_sym = sym->next; + symbols[next_index].addr = sym->sym.addr; + symbols[next_index].len = sym->sym.len; + symbols[next_index].name = sym->sym.name; + next_index += 1; + delete sym; + } +} + +void TraceReaderBase::ParseDexList(char *filename) +{ + struct stat stat_buf; + static const int kBufSize = 4096; + char buf[kBufSize]; + char current_file[kBufSize]; + + // Find an example dex file in the list of mmaps + PidEvent *event = FindMmapDexFileEvent(); + + // Reset the pid_reader to the beginning of the file. + pid_reader_->Close(); + pid_reader_->Open(filename); + + // If there were no mmapped dex files, then there is no need to parse + // the dexlist. + if (event == NULL) + return; + char *mmap_dexfile = event->path; + + // Check if the dexlist file exists. It should have the name + // "qtrace.dexlist" + char *fname = CreateTracePath(filename, ".dexlist"); + int rval = stat(fname, &stat_buf); + if (rval == -1) { + // The file does not exist + delete[] fname; + return; + } + + // Open the qtrace.dexlist file + FILE *fstream = fopen(fname, "r"); + if (fstream == NULL) { + perror(fname); + exit(1); + } + + // First pass: read all the filenames, looking for a match for the + // example mmap dex filename. Also count the files so that we + // know how big to make the hash table. + char *match = NULL; + int num_files = 0; + while (fgets(buf, kBufSize, fstream)) { + if (buf[0] != '#') + continue; + num_files += 1; + match = strstr(buf + 1, mmap_dexfile); + + // Check that the dexlist file ends with the string mmap_dexfile. + // We add one to the length of the mmap_dexfile because buf[] + // ends with a newline. The strlen(mmap_dexfile) computation + // could be moved above the loop but it should only ever be + // executed once. + if (match != NULL && strlen(match) == strlen(mmap_dexfile) + 1) + break; + } + + // Count the rest of the files + while (fgets(buf, kBufSize, fstream)) { + if (buf[0] == '#') + num_files += 1; + } + + if (match == NULL) { + fprintf(stderr, + "Cannot find the mmapped dex file '%s' in the dexlist\n", + mmap_dexfile); + exit(1); + } + delete[] mmap_dexfile; + + // The prefix length includes the leading '#'. + int prefix_len = match - buf; + + // Allocate a hash table + dex_hash_ = new HashTable<DexFileList*>(4 * num_files, NULL); + + // Reset the file stream to the beginning + rewind(fstream); + + // Second pass: read the filenames, stripping off the common prefix. + // And read all the (address, method) mappings. When we read a new + // filename, create a new DexFileList and add it to the hash table. + // Add new symbol mappings to a linked list until we have the whole + // list and then create an array for them so that we can use binary + // search on the address to find the symbol name quickly. + + // Use a linked list for storing the symbols + DexSymList *head = NULL; + DexSymList *prev = NULL; + int num_symbols = 0; + + DexFileList *dexfile = NULL; + int linenum = 0; + while (fgets(buf, kBufSize, fstream)) { + linenum += 1; + if (buf[0] == '#') { + // Everything after the '#' is a filename. + // Ignore the common prefix. + + // First, save all the symbols from the previous file (if any). + CopyDexSymbolsToArray(dexfile, head, num_symbols); + + dexfile = new DexFileList; + // Subtract one because buf[] contains a trailing newline + int pathlen = strlen(buf) - prefix_len - 1; + char *path = new char[pathlen + 1]; + strncpy(path, buf + prefix_len, pathlen); + path[pathlen] = 0; + dexfile->path = path; + dexfile->nsymbols = 0; + dexfile->symbols = NULL; + dex_hash_->Update(path, dexfile); + num_symbols = 0; + head = NULL; + prev = NULL; + continue; + } + + uint32_t addr; + int len, line; + char clazz[kBufSize], method[kBufSize], sig[kBufSize], file[kBufSize]; + if (sscanf(buf, "0x%x %d %s %s %s %s %d", + &addr, &len, clazz, method, sig, file, &line) != 7) { + fprintf(stderr, "Cannot parse line %d of file %s:\n%s", + linenum, fname, buf); + exit(1); + } + + // Concatenate the class name, method name, and signature + // plus one for the period separating the class and method. + int nchars = strlen(clazz) + strlen(method) + strlen(sig) + 1; + char *name = new char[nchars + 1]; + strcpy(name, clazz); + strcat(name, "."); + strcat(name, method); + strcat(name, sig); + + DexSymList *symbol = new DexSymList; + symbol->sym.addr = addr; + symbol->sym.len = len; + symbol->sym.name = name; + symbol->next = NULL; + + // Keep the list in the same order as the file + if (head == NULL) + head = symbol; + if (prev != NULL) + prev->next = symbol; + prev = symbol; + num_symbols += 1; + } + fclose(fstream); + + // Copy the symbols from the last file. + CopyDexSymbolsToArray(dexfile, head, num_symbols); + delete[] fname; +} + +// Extracts the pathname to a jar file (or .apk file) from the mmap pathname. +// An example mmap pathname looks something like this: +// /data/dalvik-cache/system@app@TestHarness.apk@classes.dex +// We want to convert that to this: +// /system/app/TestHarness.apk +// If the pathname is not of the expected form, then NULL is returned. +// The space for the extracted path is allocated in this routine and should +// be freed by the caller after it is no longer needed. +static char *ExtractDexPathFromMmap(const char *mmap_path) +{ + char *end = rindex(mmap_path, '@'); + if (end == NULL) + return NULL; + char *start = rindex(mmap_path, '/'); + if (start == NULL) + return NULL; + int len = end - start; + char *path = new char[len + 1]; + strncpy(path, start, len); + path[len] = 0; + + // Replace all the occurrences of '@' with '/' + for (int ii = 0; ii < len; ii++) { + if (path[ii] == '@') + path[ii] = '/'; + } + return path; +} + +void TraceReaderBase::Close() +{ + bb_reader_->Close(); + insn_reader_->Close(); + load_addr_reader_->Close(); + store_addr_reader_->Close(); + exc_reader_->Close(); + pid_reader_->Close(); + method_reader_->Close(); + internal_exc_reader_->Close(); + internal_pid_reader_->Close(); + internal_method_reader_->Close(); + fclose(static_fstream_); + static_fstream_ = NULL; +} + +void TraceReaderBase::WriteHeader(TraceHeader *header) +{ + TraceHeader swappedHeader; + + freopen(static_filename_, "r+", static_fstream_); + fseek(static_fstream_, 0, SEEK_SET); + + memcpy(&swappedHeader, header, sizeof(TraceHeader)); + + convert32(swappedHeader.version); + convert32(swappedHeader.start_sec); + convert32(swappedHeader.start_usec); + convert32(swappedHeader.pdate); + convert32(swappedHeader.ptime); + convert64(swappedHeader.num_static_bb); + convert64(swappedHeader.num_static_insn); + convert64(swappedHeader.num_dynamic_bb); + convert64(swappedHeader.num_dynamic_insn); + convert64(swappedHeader.elapsed_usecs); + + fwrite(&swappedHeader, sizeof(TraceHeader), 1, static_fstream_); +} + +// Reads the next StaticRec from the trace file (not including the list +// of instructions). On end-of-file, this function returns true. +int TraceReaderBase::ReadStatic(StaticRec *rec) +{ + int rval = fread(rec, sizeof(StaticRec), 1, static_fstream_); + if (rval != 1) { + if (feof(static_fstream_)) { + return true; + } + perror(static_filename_); + exit(1); + } + convert64(rec->bb_num); + convert32(rec->bb_addr); + convert32(rec->num_insns); + return false; +} + +// Reads "num" instructions into the array "insns" which must be large +// enough to hold the "num" instructions. +// Returns the actual number of instructions read. This will usually +// be "num" but may be less if end-of-file occurred. +int TraceReaderBase::ReadStaticInsns(int num, uint32_t *insns) +{ + if (num == 0) + return 0; + int rval = fread(insns, sizeof(uint32_t), num, static_fstream_); + + // Convert from little-endian, if necessary + for (int ii = 0; ii < num; ++ii) + convert32(insns[ii]); + + if (rval != num) { + if (feof(static_fstream_)) { + return rval; + } + perror(static_filename_); + exit(1); + } + return rval; +} + +void TraceReaderBase::TruncateLastBlock(uint32_t num_insns) +{ + uint32_t insns[kMaxInsnPerBB]; + StaticRec static_rec; + long loc = 0, prev_loc = 0; + + freopen(static_filename_, "r+", static_fstream_); + fseek(static_fstream_, sizeof(TraceHeader), SEEK_SET); + + // Find the last record + while (1) { + prev_loc = loc; + loc = ftell(static_fstream_); + + // We don't need to byte-swap static_rec here because we are just + // reading the records until we get to the last one. + int rval = fread(&static_rec, sizeof(StaticRec), 1, static_fstream_); + if (rval != 1) + break; + ReadStaticInsns(static_rec.num_insns, insns); + } + if (prev_loc != 0) { + fseek(static_fstream_, prev_loc, SEEK_SET); + static_rec.num_insns = num_insns; + + // Now we need to byte-swap, but just the field that we changed. + convert32(static_rec.num_insns); + fwrite(&static_rec, sizeof(StaticRec), 1, static_fstream_); + int fd = fileno(static_fstream_); + long len = ftell(static_fstream_); + len += num_insns * sizeof(uint32_t); + ftruncate(fd, len); + } +} + +int TraceReaderBase::FindNumInsns(uint64_t bb_num, uint64_t bb_start_time) +{ + int num_insns; + + // Read the exception trace file. "bb_recnum_" is the number of + // basic block records that have been read so far, and "exc_recnum_" + // is the record number from the exception trace. + while (!exc_end_ && exc_recnum_ < bb_recnum_) { + uint32_t current_pc, target_pc; + uint64_t time; + + exc_end_ = internal_exc_reader_->ReadExc(&time, ¤t_pc, &exc_recnum_, + &target_pc, &exc_bb_num_, + &exc_time_, &exc_num_insns_); + } + + // If an exception occurred in this basic block, then use the + // number of instructions specified in the exception record. + if (!exc_end_ && exc_recnum_ == bb_recnum_) { + num_insns = exc_num_insns_; + } else { + // Otherwise, use the number of instructions specified in the + // static basic block. + num_insns = blocks_[bb_num].rec.num_insns; + } + return num_insns; +} + +// Finds the current pid for the given time. This routine reads the pid +// trace file and assumes that the "time" parameter is monotonically +// increasing. +int TraceReaderBase::FindCurrentPid(uint64_t time) +{ + PidEvent event; + + if (time < next_pid_switch_time_) + return current_pid_; + + current_pid_ = next_pid_; + while (1) { + if (internal_pid_reader_->ReadPidEvent(&event)) { + next_pid_switch_time_ = ~0ull; + break; + } + if (event.rec_type != kPidSwitch) + continue; + if (event.time > time) { + next_pid_ = event.pid; + next_pid_switch_time_ = event.time; + break; + } + current_pid_ = event.pid; + } + return current_pid_; +} diff --git a/emulator/qtools/trace_reader.h b/emulator/qtools/trace_reader.h new file mode 100644 index 0000000..4123014 --- /dev/null +++ b/emulator/qtools/trace_reader.h @@ -0,0 +1,1408 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef TRACE_READER_H +#define TRACE_READER_H + +#include <string.h> +#include <inttypes.h> +#include <elf.h> +#include <assert.h> +#include <cxxabi.h> +#include "read_elf.h" +#include "trace_reader_base.h" +#include "hash_table.h" + +struct TraceReaderEmptyStruct { +}; + +template <class T = TraceReaderEmptyStruct> +class TraceReader : public TraceReaderBase { + public: + + struct region_entry; + typedef struct symbol_entry : public T { + typedef region_entry region_type; + + // Define flag values + static const uint32_t kIsPlt = 0x01; + static const uint32_t kIsVectorStart = 0x02; + static const uint32_t kIsVectorTable = (kIsPlt | kIsVectorStart); + static const uint32_t kIsInterpreter = 0x04; + static const uint32_t kIsMethod = 0x08; + + uint32_t addr; + + // This may hold the name of the interpreted method instead of + // the name of the native function if the native function is a + // virtual machine interpreter. + const char *name; + + // The symbol for the virtual machine interpreter, or NULL + symbol_entry *vm_sym; + region_type *region; + uint32_t flags; + } symbol_type; + + typedef struct region_entry { + // Define flag values + static const uint32_t kIsKernelRegion = 0x01; + static const uint32_t kSharedSymbols = 0x02; + static const uint32_t kIsLibraryRegion = 0x04; + static const uint32_t kIsUserMappedRegion = 0x08; + + region_entry() : refs(0), path(NULL), vstart(0), vend(0), base_addr(0), + file_offset(0), flags(0), nsymbols(0), symbols(NULL) {} + + symbol_type *LookupFunctionByName(char *name) { + // Just do a linear search + for (int ii = 0; ii < nsymbols; ++ii) { + if (strcmp(symbols[ii].name, name) == 0) + return &symbols[ii]; + } + return NULL; + } + + int refs; // reference count + char *path; + uint32_t vstart; + uint32_t vend; + uint32_t base_addr; + uint32_t file_offset; + uint32_t flags; + int nsymbols; + symbol_type *symbols; + } region_type; + + typedef typename HashTable<region_type*>::entry_type hash_entry_type; + + class ProcessState { + public: + + // The "regions" array below is a pointer to array of pointers to + // regions. The size of the pointer array is kInitialNumRegions, + // but grows if needed. There is a separate region for each mmap + // call which includes shared libraries as well as .dex and .jar + // files. In addition, there is a region for the main executable + // for this process, as well as a few regions for the kernel. + // + // If a child process is a clone of a parent process, the + // regions array is unused. Instead, the "addr_manager" pointer is + // used to find the process that is the address space manager for + // both the parent and child processes. + static const int kInitialNumRegions = 10; + + static const int kMaxMethodStackSize = 1000; + + // Define values for the ProcessState flag bits + static const int kCalledExec = 0x01; + static const int kCalledExit = 0x02; + static const int kIsClone = 0x04; + static const int kHasKernelRegion = 0x08; + static const int kHasFirstMmap = 0x10; + + ProcessState() { + cpu_time = 0; + tgid = 0; + pid = 0; + parent_pid = 0; + exit_val = 0; + flags = 0; + argc = 0; + argv = NULL; + name = NULL; + nregions = 0; + max_regions = 0; + // Don't allocate space yet until we know if we are a clone. + regions = NULL; + parent = NULL; + addr_manager = this; + next = NULL; + current_method_sym = NULL; + method_stack_top = 0; + } + + ~ProcessState() { + delete[] name; + if ((flags & kIsClone) != 0) { + return; + } + + // Free the regions. We must be careful not to free the symbols + // within each region because the symbols are sometimes shared + // between multiple regions. The TraceReader class has a hash + // table containing all the unique regions and it will free the + // region symbols in its destructor. We need to free only the + // regions and the array of region pointers. + // + // Each region is also reference-counted. The count is zero + // if no other processes are sharing this region. + for (int ii = 0; ii < nregions; ii++) { + if (regions[ii]->refs > 0) { + regions[ii]->refs -= 1; + continue; + } + + delete regions[ii]; + } + + delete[] regions; + + for (int ii = 0; ii < argc; ++ii) + delete[] argv[ii]; + delete[] argv; + } + + // Dumps the stack contents to standard output. For debugging. + void DumpStack(); + + uint64_t cpu_time; + uint64_t start_time; + uint64_t end_time; + int tgid; + int pid; + int parent_pid; + int exit_val; + uint32_t flags; + int argc; + char **argv; + char *name; + int nregions; // num regions in use + int max_regions; // max regions allocated + region_type **regions; + ProcessState *parent; + ProcessState *addr_manager; // the address space manager process + ProcessState *next; + int method_stack_top; + uint32_t method_stack[kMaxMethodStackSize]; + symbol_type *current_method_sym; + }; + + TraceReader(); + ~TraceReader(); + + void ReadKernelSymbols(const char *kernel_file); + void CopyKernelRegion(ProcessState *pstate); + void ClearRegions(ProcessState *pstate); + void CopyRegions(ProcessState *parent, ProcessState *child); + symbol_type *LookupFunction(int pid, uint32_t addr, uint64_t time); + symbol_type *GetSymbols(int *num_syms); + ProcessState *GetCurrentProcess() { return current_; } + ProcessState *GetProcesses(int *num_procs); + ProcessState *GetNextProcess(); + char *GetProcessName(int pid); + void SetRoot(const char *root) { root_ = root; } + void SetDemangle(bool demangle) { demangle_ = demangle; } + bool ReadMethodSymbol(MethodRec *method_record, + symbol_type **psym, + ProcessState **pproc); + + protected: + virtual int FindCurrentPid(uint64_t time); + + private: + + static const int kNumPids = 32768; + static const uint32_t kIncludeLocalSymbols = 0x1; + + void AddPredefinedRegion(region_type *region, const char *path, + uint32_t vstart, uint32_t vend, + uint32_t base); + void InitRegionSymbols(region_type *region, int nsymbols); + void AddRegionSymbol(region_type *region, int idx, + uint32_t addr, const char *name, + uint32_t flags); + void AddPredefinedRegions(ProcessState *pstate); + void demangle_names(int nfuncs, symbol_type *functions); + bool ReadElfSymbols(region_type *region, uint32_t flags); + void AddRegion(ProcessState *pstate, region_type *region); + region_type *FindRegion(uint32_t addr, int nregions, + region_type **regions); + symbol_type *FindFunction(uint32_t addr, int nsyms, + symbol_type *symbols, bool exact_match); + symbol_type *FindCurrentMethod(int pid, uint64_t time); + void PopulateSymbolsFromDexFile(const DexFileList *dexfile, + region_type *region); + void HandlePidEvent(PidEvent *event); + void HandleMethodRecord(ProcessState *pstate, + MethodRec *method_rec); + + int cached_pid_; + symbol_type *cached_func_; + symbol_type unknown_; + int next_pid_; + + PidEvent next_pid_event_; + ProcessState *processes_[kNumPids]; + ProcessState *current_; + MethodRec next_method_; + uint64_t function_start_time_; + const char *root_; + HashTable<region_type*> *hash_; + bool demangle_; +}; + +template<class T> +TraceReader<T>::TraceReader() +{ + static PidEvent event_no_action; + + cached_pid_ = -1; + cached_func_ = NULL; + + memset(&unknown_, 0, sizeof(symbol_type)); + unknown_.name = "(unknown)"; + next_pid_ = 0; + + memset(&event_no_action, 0, sizeof(PidEvent)); + event_no_action.rec_type = kPidNoAction; + next_pid_event_ = event_no_action; + for (int ii = 1; ii < kNumPids; ++ii) + processes_[ii] = NULL; + current_ = new ProcessState; + processes_[0] = current_; + next_method_.time = 0; + next_method_.addr = 0; + next_method_.flags = 0; + function_start_time_ = 0; + root_ = ""; + hash_ = new HashTable<region_type*>(512); + AddPredefinedRegions(current_); + demangle_ = true; +} + +template<class T> +TraceReader<T>::~TraceReader() +{ + hash_entry_type *ptr; + for (ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) { + region_type *region = ptr->value; + int nsymbols = region->nsymbols; + for (int ii = 0; ii < nsymbols; ii++) { + delete[] region->symbols[ii].name; + } + delete[] region->symbols; + delete[] region->path; + + // Do not delete the region itself here. Each region + // is reference-counted and deleted by the ProcessState + // object that owns it. + } + delete hash_; + + // Delete the ProcessState objects after the region symbols in + // the hash table above so that we still have valid region pointers + // when deleting the region symbols. + for (int ii = 0; ii < kNumPids; ++ii) { + delete processes_[ii]; + } +} + +// This function is used by the qsort() routine to sort symbols +// into increasing address order. +template<class T> +int cmp_symbol_addr(const void *a, const void *b) { + typedef typename TraceReader<T>::symbol_type stype; + + const stype *syma = static_cast<stype const *>(a); + const stype *symb = static_cast<stype const *>(b); + uint32_t addr1 = syma->addr; + uint32_t addr2 = symb->addr; + if (addr1 < addr2) + return -1; + if (addr1 > addr2) + return 1; + + // The addresses are the same, sort the symbols into + // increasing alphabetical order. But put symbols that + // that start with "_" last. + if (syma->name[0] == '_' || symb->name[0] == '_') { + // Count the number of leading underscores and sort the + // symbol with the most underscores last. + int aCount = 0; + while (syma->name[aCount] == '_') + aCount += 1; + int bCount = 0; + while (symb->name[bCount] == '_') + bCount += 1; + if (aCount < bCount) { + return -1; + } + if (aCount > bCount) { + return 1; + } + // If the symbols have the same number of underscores, then + // fall through and sort by the whole name. + } + return strcmp(syma->name, symb->name); +} + +// This function is used by the qsort() routine to sort region entries +// into increasing address order. +template<class T> +int cmp_region_addr(const void *a, const void *b) { + typedef typename TraceReader<T>::region_type rtype; + + const rtype *ma = *static_cast<rtype* const *>(a); + const rtype *mb = *static_cast<rtype* const *>(b); + uint32_t addr1 = ma->vstart; + uint32_t addr2 = mb->vstart; + if (addr1 < addr2) + return -1; + if (addr1 == addr2) + return 0; + return 1; +} + +// This routine returns a new array containing all the symbols. +template<class T> +typename TraceReader<T>::symbol_type* +TraceReader<T>::GetSymbols(int *num_syms) +{ + // Count the symbols + int nsyms = 0; + for (hash_entry_type *ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) { + region_type *region = ptr->value; + nsyms += region->nsymbols; + } + *num_syms = nsyms; + + // Allocate space + symbol_type *syms = new symbol_type[nsyms]; + symbol_type *next_sym = syms; + + // Copy the symbols + for (hash_entry_type *ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) { + region_type *region = ptr->value; + memcpy(next_sym, region->symbols, region->nsymbols * sizeof(symbol_type)); + next_sym += region->nsymbols; + } + + return syms; +} + +// This routine returns all the valid processes. +template<class T> +typename TraceReader<T>::ProcessState* +TraceReader<T>::GetProcesses(int *num_procs) +{ + // Count the valid processes + int nprocs = 0; + for (int ii = 0; ii < kNumPids; ++ii) { + if (processes_[ii]) + nprocs += 1; + } + + // Allocate a new array to hold the valid processes. + ProcessState *procs = new ProcessState[nprocs]; + + // Copy the processes to the new array. + ProcessState *pstate = procs; + for (int ii = 0; ii < kNumPids; ++ii) { + if (processes_[ii]) + memcpy(pstate++, processes_[ii], sizeof(ProcessState)); + } + + *num_procs = nprocs; + return procs; +} + +// This routine returns the next valid process, or NULL if there are no +// more valid processes. +template<class T> +typename TraceReader<T>::ProcessState* +TraceReader<T>::GetNextProcess() +{ + while (next_pid_ < kNumPids) { + if (processes_[next_pid_]) + return processes_[next_pid_++]; + next_pid_ += 1; + } + next_pid_ = 0; + return NULL; +} + +template<class T> +char* TraceReader<T>::GetProcessName(int pid) +{ + if (pid < 0 || pid >= kNumPids || processes_[pid] == NULL) + return "(unknown)"; + return processes_[pid]->name; +} + +template<class T> +void TraceReader<T>::AddPredefinedRegion(region_type *region, const char *path, + uint32_t vstart, uint32_t vend, + uint32_t base) +{ + // Copy the path to make it easy to delete later. + int len = strlen(path); + region->path = new char[len + 1]; + strcpy(region->path, path); + region->vstart = vstart; + region->vend = vend; + region->base_addr = base; + region->flags = region_type::kIsKernelRegion; +} + +template<class T> +void TraceReader<T>::InitRegionSymbols(region_type *region, int nsymbols) +{ + region->nsymbols = nsymbols; + region->symbols = new symbol_type[nsymbols]; + memset(region->symbols, 0, nsymbols * sizeof(symbol_type)); +} + +template<class T> +void TraceReader<T>::AddRegionSymbol(region_type *region, int idx, + uint32_t addr, const char *name, + uint32_t flags) +{ + region->symbols[idx].addr = addr; + region->symbols[idx].name = Strdup(name); + region->symbols[idx].vm_sym = NULL; + region->symbols[idx].region = region; + region->symbols[idx].flags = flags; +} + +template<class T> +void TraceReader<T>::AddPredefinedRegions(ProcessState *pstate) +{ + region_type *region = new region_type; + AddPredefinedRegion(region, "(bootloader)", 0, 0x14, 0); + InitRegionSymbols(region, 2); + AddRegionSymbol(region, 0, 0, "(bootloader_start)", 0); + AddRegionSymbol(region, 1, 0x14, "(bootloader_end)", 0); + AddRegion(pstate, region); + hash_->Update(region->path, region); + + region = new region_type; + AddPredefinedRegion(region, "(exception vectors)", 0xffff0000, 0xffff0500, + 0xffff0000); + InitRegionSymbols(region, 2); + AddRegionSymbol(region, 0, 0x0, "(vector_start)", + symbol_type::kIsVectorStart); + AddRegionSymbol(region, 1, 0x500, "(vector_end)", 0); + AddRegion(pstate, region); + hash_->Update(region->path, region); + + region = new region_type; + AddPredefinedRegion(region, "(atomic ops)", 0xffff0f80, 0xffff1000, + 0xffff0f80); + // Mark this region as also being mapped in user-space. + // This isn't used anywhere in this code but client code can test for + // this flag and decide whether to treat this as kernel or user code. + region->flags |= region_type::kIsUserMappedRegion; + + InitRegionSymbols(region, 4); + AddRegionSymbol(region, 0, 0x0, "(kuser_atomic_inc)", 0); + AddRegionSymbol(region, 1, 0x20, "(kuser_atomic_dec)", 0); + AddRegionSymbol(region, 2, 0x40, "(kuser_cmpxchg)", 0); + AddRegionSymbol(region, 3, 0x80, "(kuser_end)", 0); + AddRegion(pstate, region); + hash_->Update(region->path, region); +} + +template<class T> +void TraceReader<T>::ReadKernelSymbols(const char *kernel_file) +{ + region_type *region = new region_type; + // Copy the path to make it easy to delete later. + int len = strlen(kernel_file); + region->path = new char[len + 1]; + strcpy(region->path, kernel_file); + region->flags = region_type::kIsKernelRegion; + ReadElfSymbols(region, kIncludeLocalSymbols); + region->vend = 0xffff0000; + AddRegion(processes_[0], region); + processes_[0]->flags |= ProcessState::kHasKernelRegion; + hash_->Update(region->path, region); +} + +template<class T> +void TraceReader<T>::demangle_names(int nfuncs, symbol_type *functions) +{ + char *demangled; + int status; + + for (int ii = 0; ii < nfuncs; ++ii) { + demangled = NULL; + int len = strlen(functions[ii].name); + + // If we don't check for "len > 1" then the demangler will incorrectly + // expand 1-letter function names. For example, "b" becomes "bool", + // "c" becomes "char" and "d" becomes "double". Also check that the + // first character is an underscore. Otherwise, on some strings + // the demangler will try to read past the end of the string (because + // the string is not really a C++ mangled name) and valgrind will + // complain. + if (demangle_ && len > 1 && functions[ii].name[0] == '_') { + demangled = abi::__cxa_demangle(functions[ii].name, 0, NULL, + &status); + } + + if (demangled != NULL) { + delete[] functions[ii].name; + functions[ii].name = Strdup(demangled); + free(demangled); + } + } +} + +// Adds the symbols from the given ELF file to the given process. +// Returns false if the file was not an ELF file or if there was an +// error trying to read the sections of the ELF file. +template<class T> +bool TraceReader<T>::ReadElfSymbols(region_type *region, uint32_t flags) +{ + static char full_path[4096]; + Elf32_Shdr *symtab, *symstr; + Elf32_Ehdr *hdr; + Elf32_Shdr *shdr; + + full_path[0] = 0; + if (root_ && strcmp(root_, "/")) { + strcpy(full_path, root_); + } + strcat(full_path, region->path); + FILE *fobj = fopen(full_path, "r"); + if(fobj == NULL) { + EmptyRegion: + // we need to create an (unknown) symbol with address 0, otherwise some + // other parts of the trace reader will simply crash when dealing with + // an empty region + region->vstart = 0; + region->nsymbols = 1; + region->symbols = new symbol_type[1]; + memset(region->symbols, 0, sizeof(symbol_type)); + + region->symbols[0].addr = 0; + region->symbols[0].name = Strdup("(unknown)"); + region->symbols[0].vm_sym = NULL; + region->symbols[0].region = region; + region->symbols[0].flags = 0; + + if (fobj != NULL) + fclose(fobj); + return false; + } + + hdr = ReadElfHeader(fobj); + if (hdr == NULL) { + fprintf(stderr, "Cannot read ELF header from '%s'\n", full_path); + goto EmptyRegion; + } + + shdr = ReadSectionHeaders(hdr, fobj); + if(shdr == NULL) { + fprintf(stderr, "Can't read section headers from executable\n"); + goto EmptyRegion; + } + char *section_names = ReadStringTable(hdr, shdr, fobj); + + // Get the symbol table section + symtab = FindSymbolTableSection(hdr, shdr, section_names); + if (symtab == NULL || symtab->sh_size == 0) { + fprintf(stderr, "Can't read symbol table from '%s'\n", full_path); + goto EmptyRegion; + } + + // Get the symbol string table section + symstr = FindSymbolStringTableSection(hdr, shdr, section_names); + if (symstr == NULL || symstr->sh_size == 0) { + fprintf(stderr, "Can't read symbol string table from '%s'\n", full_path); + goto EmptyRegion; + } + + // Load the symbol string table data + char *symbol_names = new char[symstr->sh_size]; + ReadSection(symstr, symbol_names, fobj); + + int num_entries = symtab->sh_size / symtab->sh_entsize; + Elf32_Sym *elf_symbols = new Elf32_Sym[num_entries]; + ReadSection(symtab, elf_symbols, fobj); + AdjustElfSymbols(hdr, elf_symbols, num_entries); +#if 0 + printf("size: %d, ent_size: %d, num_entries: %d\n", + symtab->sh_size, symtab->sh_entsize, num_entries); +#endif + int nfuncs = 0; + + // Allocate space for all of the symbols for now. We will + // reallocate space for just the function symbols after we + // know how many there are. Also, make sure there is room + // for some extra symbols, including the text section names. + int num_alloc = num_entries + hdr->e_shnum + 1; + symbol_type *func_symbols = new symbol_type[num_alloc]; + memset(func_symbols, 0, num_alloc * sizeof(symbol_type)); + + // If this is the shared library for a virtual machine, then + // set the IsInterpreter flag for all symbols in that shared library. + // This will allow us to replace the symbol names with the name of + // the currently executing method on the virtual machine. + int symbol_flags = 0; + char *cp = strrchr(region->path, '/'); + if (cp != NULL) { + // Move past the '/' + cp += 1; + } else { + // There was no '/', so use the whole path + cp = region->path; + } + if (strcmp(cp, "libdvm.so") == 0) { + symbol_flags = symbol_type::kIsInterpreter; + } + + bool zero_found = false; + for (int ii = 1; ii < num_entries; ++ii) { + int idx = elf_symbols[ii].st_name; + + // If the symbol does not have a name, or if the name starts with a + // dollar sign ($), then skip it. + if (idx == 0 || symbol_names[idx] == 0 || symbol_names[idx] == '$') + continue; + + // If the section index is not executable, then skip it. + uint32_t section = elf_symbols[ii].st_shndx; + if (section == 0 || section >= hdr->e_shnum) + continue; + if ((shdr[section].sh_flags & SHF_EXECINSTR) == 0) + continue; + + uint8_t sym_type = ELF32_ST_TYPE(elf_symbols[ii].st_info); + uint8_t sym_bind = ELF32_ST_BIND(elf_symbols[ii].st_info); + + // Allow the caller to decide if we want local non-function + // symbols to be included. We currently include these symbols + // only for the kernel, where it is useful because the kernel + // has lots of assembly language labels that have meaningful names. + if ((flags & kIncludeLocalSymbols) == 0 && sym_bind == STB_LOCAL + && sym_type != STT_FUNC) { + continue; + } +#if 0 + printf("%08x %x %x %s\n", + elf_symbols[ii].st_value, + sym_bind, + sym_type, + &symbol_names[idx]); +#endif + if (sym_type != STT_FUNC && sym_type != STT_NOTYPE) + continue; + + if (elf_symbols[ii].st_value == 0) + zero_found = true; + + // The address of thumb functions seem to have the low bit set, + // even though the instructions are really at an even address. + uint32_t addr = elf_symbols[ii].st_value & ~0x1; + func_symbols[nfuncs].addr = addr; + func_symbols[nfuncs].name = Strdup(&symbol_names[idx]); + func_symbols[nfuncs].flags = symbol_flags; + + nfuncs += 1; + } + + // Add a [0, "(unknown)"] symbol pair if there is not already a + // symbol with the address zero. We don't need to reallocate space + // because we already have more than we need. + if (!zero_found) { + func_symbols[nfuncs].addr = 0; + func_symbols[nfuncs].name = Strdup("(0 unknown)"); + nfuncs += 1; + } + + // Add another entry at the end + func_symbols[nfuncs].addr = 0xffffffff; + func_symbols[nfuncs].name = Strdup("(end)"); + nfuncs += 1; + + // Add in the names of the text sections, but only if there + // are no symbols with that address already. + for (int section = 0; section < hdr->e_shnum; ++section) { + if ((shdr[section].sh_flags & SHF_EXECINSTR) == 0) + continue; + + uint32_t addr = shdr[section].sh_addr; + // Search for a symbol with a matching address. The symbols aren't + // sorted yet so we just search the whole list. + int ii; + for (ii = 0; ii < nfuncs; ++ii) { + if (addr == func_symbols[ii].addr) + break; + } + if (ii == nfuncs) { + // Symbol at address "addr" does not exist, so add the text + // section name. This will usually add the ".plt" section + // (procedure linkage table). + int idx = shdr[section].sh_name; + func_symbols[nfuncs].addr = addr; + func_symbols[nfuncs].name = Strdup(§ion_names[idx]); + if (strcmp(func_symbols[nfuncs].name, ".plt") == 0) { + func_symbols[nfuncs].flags |= symbol_type::kIsPlt; + // Change the name of the symbol to include the + // name of the library. Otherwise we will have lots + // of ".plt" symbols. + int len = strlen(region->path); + len += strlen(":.plt"); + char *name = new char[len + 1]; + strcpy(name, region->path); + strcat(name, ":.plt"); + delete[] func_symbols[nfuncs].name; + func_symbols[nfuncs].name = name; + + // Check if this is part of the virtual machine interpreter + char *cp = strrchr(region->path, '/'); + if (cp != NULL) { + // Move past the '/' + cp += 1; + } else { + // There was no '/', so use the whole path + cp = region->path; + } + if (strcmp(cp, "libdvm.so") == 0) { + func_symbols[nfuncs].flags |= symbol_type::kIsInterpreter; + } + } + nfuncs += 1; + } + } + + // Allocate just the space we need now that we know exactly + // how many symbols we have. + symbol_type *functions = new symbol_type[nfuncs]; + + // Copy the symbols to the functions array + memcpy(functions, func_symbols, nfuncs * sizeof(symbol_type)); + delete[] func_symbols; + + // Assign the region pointers + for (int ii = 0; ii < nfuncs; ++ii) { + functions[ii].region = region; + } + + // Sort the symbols into increasing address order + qsort(functions, nfuncs, sizeof(symbol_type), cmp_symbol_addr<T>); + + // If there are multiple symbols with the same address, then remove + // the duplicates. First, count the number of duplicates. + uint32_t prev_addr = ~0; + int num_duplicates = 0; + for (int ii = 0; ii < nfuncs; ++ii) { + if (prev_addr == functions[ii].addr) + num_duplicates += 1; + prev_addr = functions[ii].addr; + } + + if (num_duplicates > 0) { + int num_uniq = nfuncs - num_duplicates; + + // Allocate space for the unique functions + symbol_type *uniq_functions = new symbol_type[num_uniq]; + + // Copy the unique functions + prev_addr = ~0; + int next_uniq = 0; + for (int ii = 0; ii < nfuncs; ++ii) { + if (prev_addr == functions[ii].addr) { + delete[] functions[ii].name; + continue; + } + memcpy(&uniq_functions[next_uniq++], &functions[ii], + sizeof(symbol_type)); + prev_addr = functions[ii].addr; + } + assert(next_uniq == num_uniq); + + delete[] functions; + functions = uniq_functions; + nfuncs = num_uniq; + } + + // Finally, demangle all of the symbol names + demangle_names(nfuncs, functions); + + uint32_t min_addr = 0; + if (!zero_found) + min_addr = functions[1].addr; + if (region->vstart == 0) + region->vstart = min_addr; + region->nsymbols = nfuncs; + region->symbols = functions; + +#if 0 + printf("%s num symbols: %d min_addr: 0x%x\n", region->path, nfuncs, min_addr); + for (int ii = 0; ii < nfuncs; ++ii) { + printf("0x%08x %s\n", functions[ii].addr, functions[ii].name); + } +#endif + delete[] elf_symbols; + delete[] symbol_names; + delete[] section_names; + delete[] shdr; + delete hdr; + fclose(fobj); + + return true; +} + +template<class T> +void TraceReader<T>::CopyKernelRegion(ProcessState *pstate) +{ + ProcessState *manager = pstate->addr_manager; + if (manager->flags & ProcessState::kHasKernelRegion) + return; + + int nregions = processes_[0]->nregions; + region_type **regions = processes_[0]->regions; + for (int ii = 0; ii < nregions; ii++) { + if (regions[ii]->flags & region_type::kIsKernelRegion) { + AddRegion(manager, regions[ii]); + regions[ii]->refs += 1; + } + } + manager->flags |= ProcessState::kHasKernelRegion; +} + +template<class T> +void TraceReader<T>::ClearRegions(ProcessState *pstate) +{ + assert(pstate->pid != 0); + int nregions = pstate->nregions; + region_type **regions = pstate->regions; + + // Decrement the reference count on all the regions + for (int ii = 0; ii < nregions; ii++) { + if (regions[ii]->refs > 0) { + regions[ii]->refs -= 1; + continue; + } + + delete regions[ii]; + } + delete[] pstate->regions; + pstate->regions = NULL; + pstate->nregions = 0; + pstate->max_regions = 0; + pstate->addr_manager = pstate; + pstate->flags &= ~ProcessState::kIsClone; + pstate->flags &= ~ProcessState::kHasKernelRegion; + CopyKernelRegion(pstate); +} + +template<class T> +void TraceReader<T>::AddRegion(ProcessState *pstate, region_type *region) +{ + ProcessState *manager = pstate->addr_manager; + if (manager->regions == NULL) { + manager->max_regions = ProcessState::kInitialNumRegions; + manager->regions = new region_type*[manager->max_regions]; + manager->nregions = 0; + } + + // Check if we need to grow the array + int nregions = manager->nregions; + int max_regions = manager->max_regions; + if (nregions >= max_regions) { + max_regions <<= 1; + manager->max_regions = max_regions; + region_type **regions = new region_type*[max_regions]; + for (int ii = 0; ii < nregions; ii++) { + regions[ii] = manager->regions[ii]; + } + delete[] manager->regions; + manager->regions = regions; + } + + // Add the new region to the end of the array and resort + manager->regions[nregions] = region; + nregions += 1; + manager->nregions = nregions; + + // Resort the regions into increasing start address + qsort(manager->regions, nregions, sizeof(region_type*), cmp_region_addr<T>); +} + +template<class T> +void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child) +{ + // Copy the parent's address space + ProcessState *manager = parent->addr_manager; + int nregions = manager->nregions; + child->nregions = nregions; + child->max_regions = manager->max_regions; + region_type **regions = new region_type*[manager->max_regions]; + child->regions = regions; + memcpy(regions, manager->regions, nregions * sizeof(region_type*)); + + // Increment the reference count on all the regions + for (int ii = 0; ii < nregions; ii++) { + regions[ii]->refs += 1; + } +} + +template<class T> +typename TraceReader<T>::region_type * +TraceReader<T>::FindRegion(uint32_t addr, int nregions, region_type **regions) +{ + int high = nregions; + int low = -1; + while (low + 1 < high) { + int middle = (high + low) / 2; + uint32_t middle_addr = regions[middle]->vstart; + if (middle_addr == addr) + return regions[middle]; + if (middle_addr > addr) + high = middle; + else + low = middle; + } + + // If we get here then we did not find an exact address match. So use + // the closest region address that is less than the given address. + if (low < 0) + low = 0; + return regions[low]; +} + +template<class T> +typename TraceReader<T>::symbol_type * +TraceReader<T>::FindFunction(uint32_t addr, int nsyms, symbol_type *symbols, + bool exact_match) +{ + int high = nsyms; + int low = -1; + while (low + 1 < high) { + int middle = (high + low) / 2; + uint32_t middle_addr = symbols[middle].addr; + if (middle_addr == addr) + return &symbols[middle]; + if (middle_addr > addr) + high = middle; + else + low = middle; + } + + // If we get here then we did not find an exact address match. So use + // the closest function address that is less than the given address. + // We added a symbol with address zero so if there is no known + // function containing the given address, then we will return the + // "(unknown)" symbol. + if (low >= 0 && !exact_match) + return &symbols[low]; + return NULL; +} + +template<class T> +typename TraceReader<T>::symbol_type * +TraceReader<T>::LookupFunction(int pid, uint32_t addr, uint64_t time) +{ + // Check if the previous match is still a good match. + if (cached_pid_ == pid) { + uint32_t vstart = cached_func_->region->vstart; + uint32_t vend = cached_func_->region->vend; + if (addr >= vstart && addr < vend) { + uint32_t sym_addr = addr - cached_func_->region->base_addr; + if (sym_addr >= cached_func_->addr + && sym_addr < (cached_func_ + 1)->addr) { + // If this function is the virtual machine interpreter, then + // read the method trace to find the "real" method name based + // on the current time and pid. + if (cached_func_->flags & symbol_type::kIsInterpreter) { + symbol_type *sym = FindCurrentMethod(pid, time); + if (sym != NULL) { + sym->vm_sym = cached_func_; + return sym; + } + } + return cached_func_; + } + } + } + + ProcessState *pstate = processes_[pid]; + if (pstate == NULL) { + // There is no process state for the specified pid. + // This should never happen. + cached_pid_ = -1; + cached_func_ = NULL; + return NULL; + } + ProcessState *manager = pstate->addr_manager; + cached_pid_ = pid; + region_type *region = FindRegion(addr, manager->nregions, manager->regions); + uint32_t sym_addr = addr - region->base_addr; + + cached_func_ = FindFunction(sym_addr, region->nsymbols, region->symbols, + false /* no exact match */); + if (cached_func_ != NULL) { + cached_func_->region = region; + + // If this function is the virtual machine interpreter, then + // read the method trace to find the "real" method name based + // on the current time and pid. + if (cached_func_->flags & symbol_type::kIsInterpreter) { + symbol_type *sym = FindCurrentMethod(pid, time); + if (sym != NULL) { + sym->vm_sym = cached_func_; + return sym; + } + } + } + + return cached_func_; +} + +template <class T> +void TraceReader<T>::HandlePidEvent(PidEvent *event) +{ + switch (event->rec_type) { + case kPidFork: + case kPidClone: + // event->pid is the process id of the child + if (event->pid >= kNumPids) { + fprintf(stderr, "Error: pid (%d) too large\n", event->pid); + exit(1); + } + // Create a new ProcessState struct for the child + // and link it in at the front of the list for that + // pid. + { + ProcessState *child = new ProcessState; + processes_[event->pid] = child; + child->pid = event->pid; + child->tgid = event->tgid; + + // Link the new child at the front of the list (only needed if + // pids wrap around, which will probably never happen when + // tracing because it would take so long). + child->next = processes_[event->pid]; + child->parent_pid = current_->pid; + child->parent = current_; + child->start_time = event->time; + child->name = Strdup(current_->name); + if (event->rec_type == kPidFork) { + CopyRegions(current_, child); + } else { + // Share the parent's address space + child->flags |= ProcessState::kIsClone; + + // The address space manager for the clone is the same + // as the address space manager for the parent. This works + // even if the child later clones itself. + child->addr_manager = current_->addr_manager; + } + } + break; + case kPidSwitch: + // event->pid is the process id of the process we are + // switching to. + { + uint64_t elapsed = event->time - function_start_time_; + function_start_time_ = event->time; + current_->cpu_time += elapsed; + } + if (current_->flags & ProcessState::kCalledExit) + current_->end_time = event->time; + + if (event->pid >= kNumPids) { + fprintf(stderr, "Error: pid (%d) too large\n", event->pid); + exit(1); + } + + // If the process we are switching to does not exist, then + // create one. This can happen because the tracing code does + // not start tracing from the very beginning of the kernel. + current_ = processes_[event->pid]; + if (current_ == NULL) { + current_ = new ProcessState; + processes_[event->pid] = current_; + current_->pid = event->pid; + current_->start_time = event->time; + CopyKernelRegion(current_); + } +#if 0 + { + printf("switching to p%d\n", current_->pid); + ProcessState *manager = current_->addr_manager; + for (int ii = 0; ii < manager->nregions; ++ii) { + printf(" %08x - %08x offset: %d nsyms: %4d %s\n", + manager->regions[ii]->vstart, + manager->regions[ii]->vend, + manager->regions[ii]->file_offset, + manager->regions[ii]->nsymbols, + manager->regions[ii]->path); + } + } +#endif + break; + case kPidExit: + current_->exit_val = event->pid; + current_->flags |= ProcessState::kCalledExit; + break; + case kPidMmap: + { + region_type *region; + region_type *existing_region = hash_->Find(event->path); + if (existing_region == NULL || existing_region->vstart != event->vstart) { + // Create a new region and add it to the current process' + // address space. + region = new region_type; + + // The event->path is allocated by ReadPidEvent() and owned + // by us. + region->path = event->path; + region->vstart = event->vstart; + region->vend = event->vend; + region->file_offset = event->offset; + if (existing_region == NULL) { + DexFileList *dexfile = dex_hash_->Find(event->path); + if (dexfile != NULL) { + PopulateSymbolsFromDexFile(dexfile, region); + } else { + ReadElfSymbols(region, 0); + } + hash_->Update(region->path, region); + } else { + region->nsymbols = existing_region->nsymbols; + region->symbols = existing_region->symbols; + region->path = existing_region->path; + delete[] event->path; + region->flags |= region_type::kSharedSymbols; + } + + // The base_addr is subtracted from an address before the + // symbol name lookup and is either zero or event->vstart. + // HACK: Determine if base_addr is non-zero by looking at the + // second symbol address (skip the first symbol because that is + // the special symbol "(unknown)" with an address of zero). + if (region->nsymbols > 2 && region->symbols[1].addr < event->vstart) + region->base_addr = event->vstart; + + // Treat all mmapped regions after the first as "libraries". + // Profiling tools can test for this property. + if (current_->flags & ProcessState::kHasFirstMmap) + region->flags |= region_type::kIsLibraryRegion; + else + current_->flags |= ProcessState::kHasFirstMmap; +#if 0 + printf("%s vstart: 0x%x vend: 0x%x offset: 0x%x\n", + region->path, region->vstart, region->vend, region->file_offset); +#endif + } else { + region = existing_region; + region->refs += 1; + delete[] event->path; + } + AddRegion(current_, region); + } + break; + case kPidExec: + if (current_->argc > 0) { + for (int ii = 0; ii < current_->argc; ii++) { + delete[] current_->argv[ii]; + } + delete[] current_->argv; + } + delete[] current_->name; + + current_->argc = event->argc; + current_->argv = event->argv; + current_->name = Strdup(current_->argv[0]); + current_->flags |= ProcessState::kCalledExec; + ClearRegions(current_); + break; + case kPidName: + case kPidKthreadName: + { + ProcessState *pstate = processes_[event->pid]; + if (pstate == NULL) { + pstate = new ProcessState; + if (event->rec_type == kPidKthreadName) { + pstate->tgid = event->tgid; + } + pstate->pid = event->pid; + pstate->start_time = event->time; + processes_[event->pid] = pstate; + CopyKernelRegion(pstate); + } else { + delete[] pstate->name; + } + pstate->name = event->path; + } + break; + case kPidNoAction: + break; + case kPidSymbolAdd: + delete[] event->path; + break; + case kPidSymbolRemove: + break; + } +} + +// Finds the current pid for the given time. This routine reads the pid +// trace file and assumes that the "time" parameter is monotonically +// increasing. +template <class T> +int TraceReader<T>::FindCurrentPid(uint64_t time) +{ + if (time < next_pid_event_.time) + return current_->pid; + + while (1) { + HandlePidEvent(&next_pid_event_); + + if (internal_pid_reader_->ReadPidEvent(&next_pid_event_)) { + next_pid_event_.time = ~0ull; + break; + } + if (next_pid_event_.time > time) + break; + } + return current_->pid; +} + +template <class T> +void TraceReader<T>::ProcessState::DumpStack() +{ + for (int ii = 0; ii < method_stack_top; ii++) { + printf("%2d: 0x%08x\n", ii, method_stack[ii]); + } +} + +template <class T> +void TraceReader<T>::HandleMethodRecord(ProcessState *pstate, + MethodRec *method_rec) +{ + uint32_t addr; + int top = pstate->method_stack_top; + if (method_rec->flags == kMethodEnter) { + // Push this method on the stack + if (top >= pstate->kMaxMethodStackSize) { + fprintf(stderr, "Stack overflow at time %llu\n", method_rec->time); + exit(1); + } + pstate->method_stack[top] = method_rec->addr; + pstate->method_stack_top = top + 1; + addr = method_rec->addr; + } else { + if (top <= 0) { + // If the stack underflows, then set the current method to NULL. + pstate->current_method_sym = NULL; + return; + } + top -= 1; + addr = pstate->method_stack[top]; + if (addr != method_rec->addr) { + fprintf(stderr, + "Stack method (0x%x) at index %d does not match trace record (0x%x) at time %llu\n", + addr, top, method_rec->addr, method_rec->time); + for (int ii = 0; ii <= top; ii++) { + fprintf(stderr, " %d: 0x%x\n", ii, pstate->method_stack[ii]); + } + exit(1); + } + + pstate->method_stack_top = top; + if (top == 0) { + // When we empty the stack, set the current method to NULL + pstate->current_method_sym = NULL; + return; + } + addr = pstate->method_stack[top - 1]; + } + ProcessState *manager = pstate->addr_manager; + region_type *region = FindRegion(addr, manager->nregions, manager->regions); + uint32_t sym_addr = addr - region->base_addr; + symbol_type *sym = FindFunction(sym_addr, region->nsymbols, + region->symbols, true /* exact match */); + + pstate->current_method_sym = sym; + if (sym != NULL) { + sym->region = region; + } +} + +template <class T> +typename TraceReader<T>::symbol_type* +TraceReader<T>::FindCurrentMethod(int pid, uint64_t time) +{ + ProcessState *procState = processes_[pid]; + + if (time < next_method_.time) { + return procState->current_method_sym; + } + + while (1) { + if (next_method_.time != 0) { + // We may have to process methods from a different pid so use + // a local variable here so that we don't overwrite procState. + ProcessState *pState = processes_[next_method_.pid]; + HandleMethodRecord(pState, &next_method_); + } + + if (internal_method_reader_->ReadMethod(&next_method_)) { + next_method_.time = ~0ull; + break; + } + if (next_method_.time > time) + break; + } + return procState->current_method_sym; +} + +template <class T> +void TraceReader<T>::PopulateSymbolsFromDexFile(const DexFileList *dexfile, + region_type *region) + +{ + int nsymbols = dexfile->nsymbols; + DexSym *dexsyms = dexfile->symbols; + region->nsymbols = nsymbols + 1; + symbol_type *symbols = new symbol_type[nsymbols + 1]; + memset(symbols, 0, (nsymbols + 1) * sizeof(symbol_type)); + region->symbols = symbols; + for (int ii = 0; ii < nsymbols; ii++) { + symbols[ii].addr = dexsyms[ii].addr; + symbols[ii].name = Strdup(dexsyms[ii].name); + symbols[ii].vm_sym = NULL; + symbols[ii].region = region; + symbols[ii].flags = symbol_type::kIsMethod; + } + + // Add an entry at the end with an address of 0xffffffff. This + // is required for LookupFunction() to work. + symbol_type *symbol = &symbols[nsymbols]; + symbol->addr = 0xffffffff; + symbol->name = Strdup("(end)"); + symbol->vm_sym = NULL; + symbol->region = region; + symbol->flags = symbol_type::kIsMethod; +} + +template <class T> +bool TraceReader<T>::ReadMethodSymbol(MethodRec *method_record, + symbol_type **psym, + ProcessState **pproc) +{ + if (internal_method_reader_->ReadMethod(&next_method_)) { + return true; + } + + // Copy the whole MethodRec struct + *method_record = next_method_; + + uint64_t time = next_method_.time; + + // Read the pid trace file up to this point to make sure the + // process state is valid. + FindCurrentPid(time); + + ProcessState *pstate = processes_[next_method_.pid]; + *pproc = pstate; + HandleMethodRecord(pstate, &next_method_); + *psym = pstate->current_method_sym; + return false; +} + +#endif /* TRACE_READER_H */ diff --git a/emulator/qtools/trace_reader_base.h b/emulator/qtools/trace_reader_base.h new file mode 100644 index 0000000..281d085 --- /dev/null +++ b/emulator/qtools/trace_reader_base.h @@ -0,0 +1,332 @@ +// Copyright 2006 The Android Open Source Project + +#ifndef TRACE_READER_BASE_H +#define TRACE_READER_BASE_H + +#include <inttypes.h> +#include "trace_common.h" +#include "hash_table.h" + +class BBReader; +class InsnReader; +class AddrReader; +class ExcReader; +class PidReader; +class MethodReader; + +struct StaticRec { + uint64_t bb_num; + uint32_t bb_addr; + uint32_t num_insns; +}; + +struct StaticBlock { + StaticRec rec; + uint32_t *insns; +}; + +struct BBEvent { + uint64_t time; + uint64_t bb_num; + uint32_t bb_addr; + uint32_t *insns; + int num_insns; + int pid; + int is_thumb; +}; + +struct PidEvent { + uint64_t time; + int rec_type; // record type: fork, context switch, exit ... + int tgid; // thread group id + int pid; // for fork: child pid; for switch: next pid; + // for exit: exit value + uint32_t vstart; // virtual start address (only used with mmap) + uint32_t vend; // virtual end address (only used with mmap) + uint32_t offset; // virtual file offset (only used with mmap) + + // Dynamically allocated path to executable (or lib). In the case of + // an mmapped dex file, the path is modified to be more useful for + // comparing against the output of dexlist. For example, instead of this: + // /data/dalvik-cache/system@app@TestHarness.apk@classes.dex + // We convert to this: + // /system/app/TestHarness.apk + char *path; + char *mmap_path; // unmodified mmap path + int argc; // number of args + char **argv; // dynamically allocated array of args +}; + +struct MethodRec { + uint64_t time; + uint32_t addr; + int pid; + int flags; +}; + +struct DexSym { + uint32_t addr; + int len; + char *name; +}; + +struct DexFileList { + char *path; + int nsymbols; + DexSym *symbols; +}; + +class TraceReaderBase { + public: + TraceReaderBase(); + virtual ~TraceReaderBase(); + + friend class BBReader; + + void Open(char *filename); + void Close(); + void WriteHeader(TraceHeader *header); + inline bool ReadBB(BBEvent *event); + int ReadStatic(StaticRec *rec); + int ReadStaticInsns(int num, uint32_t *insns); + TraceHeader *GetHeader() { return header_; } + inline uint64_t ReadInsnTime(uint64_t min_time); + void TruncateLastBlock(uint32_t num_insns); + inline bool ReadAddr(uint64_t *time, uint32_t *addr, int *flags); + inline bool ReadExc(uint64_t *time, uint32_t *current_pc, + uint64_t *recnum, uint32_t *target_pc, + uint64_t *bb_num, uint64_t *bb_start_time, + int *num_insns); + inline bool ReadPidEvent(PidEvent *event); + inline bool ReadMethod(MethodRec *method_record); + StaticBlock *GetStaticBlock(uint64_t bb_num) { return &blocks_[bb_num]; } + uint32_t *GetInsns(uint64_t bb_num) { return blocks_[bb_num].insns; } + uint32_t GetBBAddr(uint64_t bb_num) { + return blocks_[bb_num].rec.bb_addr & ~1; + } + int GetIsThumb(uint64_t bb_num) { + return blocks_[bb_num].rec.bb_addr & 1; + } + void SetPostProcessing(bool val) { post_processing_ = val; } + + protected: + virtual int FindCurrentPid(uint64_t time); + int current_pid_; + int next_pid_; + uint64_t next_pid_switch_time_; + PidReader *internal_pid_reader_; + MethodReader *internal_method_reader_; + HashTable<DexFileList*> *dex_hash_; + + private: + int FindNumInsns(uint64_t bb_num, uint64_t bb_start_time); + void ReadTraceHeader(FILE *fstream, char *filename, + char *tracename, TraceHeader *header); + PidEvent *FindMmapDexFileEvent(); + void ParseDexList(char *filename); + + char *static_filename_; + FILE *static_fstream_; + TraceHeader *header_; + BBReader *bb_reader_; + InsnReader *insn_reader_; + AddrReader *load_addr_reader_; + AddrReader *store_addr_reader_; + ExcReader *exc_reader_; + PidReader *pid_reader_; + MethodReader *method_reader_; + ExcReader *internal_exc_reader_; + StaticBlock *blocks_; + bool exc_end_; + uint64_t bb_recnum_; + uint64_t exc_recnum_; + uint64_t exc_bb_num_; + uint64_t exc_time_; + int exc_num_insns_; + bool post_processing_; + + bool load_eof_; + uint64_t load_time_; + uint32_t load_addr_; + bool store_eof_; + uint64_t store_time_; + uint32_t store_addr_; +}; + +class Decoder; + +class BBReader { + public: + explicit BBReader(TraceReaderBase *trace); + ~BBReader(); + void Open(char *filename); + void Close(); + bool ReadBB(BBEvent *event); + + private: + struct TimeRec { + BBRec bb_rec; + uint64_t next_time; + }; + + struct Future { + Future *next; + TimeRec bb; + }; + + inline Future *AllocFuture(); + inline void FreeFuture(Future *future); + inline void InsertFuture(Future *future); + inline int DecodeNextRec(); + + TimeRec nextrec_; + Future futures_[kMaxNumBasicBlocks]; + Future *head_; + Future *free_; + Decoder *decoder_; + bool is_eof_; + TraceReaderBase *trace_; +}; + +class InsnReader { + public: + InsnReader(); + ~InsnReader(); + + void Open(char *filename); + void Close(); + uint64_t ReadInsnTime(uint64_t min_time); + + private: + Decoder *decoder_; + uint64_t prev_time_; + uint64_t time_diff_; + int repeat_; +}; + +class AddrReader { + public: + AddrReader(); + ~AddrReader(); + + bool Open(char *filename, char *suffix); + void Close(); + bool ReadAddr(uint64_t *time, uint32_t *addr); + + private: + Decoder *decoder_; + uint32_t prev_addr_; + uint64_t prev_time_; + bool opened_; // true after file is opened +}; + +class ExcReader { + public: + ExcReader(); + ~ExcReader(); + + void Open(char *filename); + void Close(); + bool ReadExc(uint64_t *time, uint32_t *current_pc, + uint64_t *recnum, uint32_t *target_pc, + uint64_t *bb_num, uint64_t *bb_start_time, + int *num_insns); + + private: + Decoder *decoder_; + uint64_t prev_time_; + uint64_t prev_recnum_; +}; + +class PidReader { + public: + PidReader(); + ~PidReader(); + + void Open(char *filename); + void Close(); + bool ReadPidEvent(struct PidEvent *event); + void Dispose(struct PidEvent *event); + + private: + Decoder *decoder_; + uint64_t prev_time_; +}; + +class MethodReader { + public: + MethodReader(); + ~MethodReader(); + + bool Open(char *filename); + void Close(); + bool ReadMethod(MethodRec *method_record); + + private: + Decoder *decoder_; + uint64_t prev_time_; + uint32_t prev_addr_; + int32_t prev_pid_; + bool opened_; // true after file is opened +}; + +// Reads the next dynamic basic block from the trace. +// Returns true on end-of-file. +inline bool TraceReaderBase::ReadBB(BBEvent *event) +{ + bb_recnum_ += 1; + return bb_reader_->ReadBB(event); +} + +inline uint64_t TraceReaderBase::ReadInsnTime(uint64_t min_time) +{ + return insn_reader_->ReadInsnTime(min_time); +} + +inline bool TraceReaderBase::ReadAddr(uint64_t *time, uint32_t *addr, int *flags) +{ + if (load_eof_ && store_eof_) + return true; + + if (store_eof_ || (!load_eof_ && load_time_ <= store_time_)) { + *time = load_time_; + *addr = load_addr_; + *flags = 0; + load_eof_ = load_addr_reader_->ReadAddr(&load_time_, &load_addr_); + } else { + *time = store_time_; + *addr = store_addr_; + *flags = 1; + store_eof_ = store_addr_reader_->ReadAddr(&store_time_, &store_addr_); + } + return false; +} + +inline bool TraceReaderBase::ReadExc(uint64_t *time, uint32_t *current_pc, + uint64_t *recnum, uint32_t *target_pc, + uint64_t *bb_num, uint64_t *bb_start_time, + int *num_insns) +{ + return exc_reader_->ReadExc(time, current_pc, recnum, target_pc, bb_num, + bb_start_time, num_insns); +} + +inline bool TraceReaderBase::ReadPidEvent(PidEvent *event) +{ + return pid_reader_->ReadPidEvent(event); +} + +inline bool TraceReaderBase::ReadMethod(MethodRec *method_record) +{ + return method_reader_->ReadMethod(method_record); +} + +// Duplicates a string, allocating space using new[]. +inline char * Strdup(const char *src) { + int len = strlen(src); + char *copy = new char[len + 1]; + strcpy(copy, src); + return copy; +} + +#endif /* TRACE_READER_BASE_H */ |