tracechild.cc revision 4955
16313Sgblack@eecs.umich.edu/* 212477SCurtis.Dunham@arm.com * Copyright (c) 2007 The Regents of The University of Michigan 37093Sgblack@eecs.umich.edu * All rights reserved. 47093Sgblack@eecs.umich.edu * 57093Sgblack@eecs.umich.edu * Redistribution and use in source and binary forms, with or without 67093Sgblack@eecs.umich.edu * modification, are permitted provided that the following conditions are 77093Sgblack@eecs.umich.edu * met: redistributions of source code must retain the above copyright 87093Sgblack@eecs.umich.edu * notice, this list of conditions and the following disclaimer; 97093Sgblack@eecs.umich.edu * redistributions in binary form must reproduce the above copyright 107093Sgblack@eecs.umich.edu * notice, this list of conditions and the following disclaimer in the 117093Sgblack@eecs.umich.edu * documentation and/or other materials provided with the distribution; 127093Sgblack@eecs.umich.edu * neither the name of the copyright holders nor the names of its 137093Sgblack@eecs.umich.edu * contributors may be used to endorse or promote products derived from 146313Sgblack@eecs.umich.edu * this software without specific prior written permission. 156313Sgblack@eecs.umich.edu * 166313Sgblack@eecs.umich.edu * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 176313Sgblack@eecs.umich.edu * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 186313Sgblack@eecs.umich.edu * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 196313Sgblack@eecs.umich.edu * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 206313Sgblack@eecs.umich.edu * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 216313Sgblack@eecs.umich.edu * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 226313Sgblack@eecs.umich.edu * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 236313Sgblack@eecs.umich.edu * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 246313Sgblack@eecs.umich.edu * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 256313Sgblack@eecs.umich.edu * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 266313Sgblack@eecs.umich.edu * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 276313Sgblack@eecs.umich.edu * 286313Sgblack@eecs.umich.edu * Authors: Gabe Black 296313Sgblack@eecs.umich.edu */ 306313Sgblack@eecs.umich.edu 316313Sgblack@eecs.umich.edu#include <iostream> 326313Sgblack@eecs.umich.edu#include <iomanip> 336313Sgblack@eecs.umich.edu#include <errno.h> 346313Sgblack@eecs.umich.edu#include <sys/ptrace.h> 356313Sgblack@eecs.umich.edu#include <stdint.h> 366313Sgblack@eecs.umich.edu 376313Sgblack@eecs.umich.edu#include "tracechild_amd64.hh" 386313Sgblack@eecs.umich.edu 396313Sgblack@eecs.umich.eduusing namespace std; 406313Sgblack@eecs.umich.edu 416313Sgblack@eecs.umich.educhar * AMD64TraceChild::regNames[numregs] = { 426313Sgblack@eecs.umich.edu //GPRs 436313Sgblack@eecs.umich.edu "rax", "rbx", "rcx", "rdx", 447404SAli.Saidi@ARM.com //Index registers 456313Sgblack@eecs.umich.edu "rsi", "rdi", 4610461SAndreas.Sandberg@ARM.com //Base pointer and stack pointer 4712479SCurtis.Dunham@arm.com "rbp", "rsp", 486333Sgblack@eecs.umich.edu //New 64 bit mode registers 4910037SARM gem5 Developers "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", 507404SAli.Saidi@ARM.com //Segmentation registers 516313Sgblack@eecs.umich.edu "cs", "ds", "es", "fs", "gs", "ss", "fs_base", "gs_base", 5212109SRekai.GonzalezAlberquilla@arm.com //PC 538232Snate@binkert.org "rip", 5412109SRekai.GonzalezAlberquilla@arm.com //Flags 559384SAndreas.Sandberg@arm.com "eflags"}; 5611165SRekai.GonzalezAlberquilla@arm.com 576313Sgblack@eecs.umich.edubool AMD64TraceChild::sendState(int socket) 589384SAndreas.Sandberg@arm.com{ 5910461SAndreas.Sandberg@ARM.com uint64_t regVal = 0; 606333Sgblack@eecs.umich.edu for(int x = 0; x <= R15; x++) 616313Sgblack@eecs.umich.edu { 626313Sgblack@eecs.umich.edu regVal = getRegVal(x); 636313Sgblack@eecs.umich.edu if(write(socket, ®Val, sizeof(regVal)) == -1) 646313Sgblack@eecs.umich.edu { 656313Sgblack@eecs.umich.edu cerr << "Write failed! " << strerror(errno) << endl; 669384SAndreas.Sandberg@arm.com tracing = false; 676313Sgblack@eecs.umich.edu return false; 686313Sgblack@eecs.umich.edu } 6910037SARM gem5 Developers } 7010037SARM gem5 Developers regVal = getRegVal(RIP); 7110037SARM gem5 Developers if(write(socket, ®Val, sizeof(regVal)) == -1) 7211165SRekai.GonzalezAlberquilla@arm.com { 7311165SRekai.GonzalezAlberquilla@arm.com cerr << "Write failed! " << strerror(errno) << endl; 7412109SRekai.GonzalezAlberquilla@arm.com tracing = false; 7511165SRekai.GonzalezAlberquilla@arm.com return false; 7610461SAndreas.Sandberg@ARM.com } 7710461SAndreas.Sandberg@ARM.com return true; 7810461SAndreas.Sandberg@ARM.com} 7910461SAndreas.Sandberg@ARM.com 8010461SAndreas.Sandberg@ARM.comint64_t AMD64TraceChild::getRegs(user_regs_struct & myregs, int num) 8110461SAndreas.Sandberg@ARM.com{ 8210844Sandreas.sandberg@arm.com assert(num < numregs && num >= 0); 8310844Sandreas.sandberg@arm.com switch(num) 8410844Sandreas.sandberg@arm.com { 8510037SARM gem5 Developers //GPRs 8611771SCurtis.Dunham@arm.com case RAX: return myregs.rax; 8710037SARM gem5 Developers case RBX: return myregs.rbx; 8810037SARM gem5 Developers case RCX: return myregs.rcx; 8910037SARM gem5 Developers case RDX: return myregs.rdx; 9010037SARM gem5 Developers //Index registers 9110037SARM gem5 Developers case RSI: return myregs.rsi; 9210037SARM gem5 Developers case RDI: return myregs.rdi; 9312478SCurtis.Dunham@arm.com //Base pointer and stack pointer 9410037SARM gem5 Developers case RBP: return myregs.rbp; 9512477SCurtis.Dunham@arm.com case RSP: return myregs.rsp; 9612477SCurtis.Dunham@arm.com //New 64 bit mode registers 9712478SCurtis.Dunham@arm.com case R8: return myregs.r8; 9812478SCurtis.Dunham@arm.com case R9: return myregs.r9; 9912478SCurtis.Dunham@arm.com case R10: return myregs.r10; 10012478SCurtis.Dunham@arm.com case R11: return myregs.r11; 10112478SCurtis.Dunham@arm.com case R12: return myregs.r12; 10212478SCurtis.Dunham@arm.com case R13: return myregs.r13; 10312478SCurtis.Dunham@arm.com case R14: return myregs.r14; 10412478SCurtis.Dunham@arm.com case R15: return myregs.r15; 10512478SCurtis.Dunham@arm.com //Segmentation registers 10612478SCurtis.Dunham@arm.com case CS: return myregs.cs; 10712478SCurtis.Dunham@arm.com case DS: return myregs.ds; 10812478SCurtis.Dunham@arm.com case ES: return myregs.es; 10912478SCurtis.Dunham@arm.com case FS: return myregs.fs; 11012478SCurtis.Dunham@arm.com case GS: return myregs.gs; 11112478SCurtis.Dunham@arm.com case SS: return myregs.ss; 11212478SCurtis.Dunham@arm.com case FS_BASE: return myregs.fs_base; 11310037SARM gem5 Developers case GS_BASE: return myregs.gs_base; 11410037SARM gem5 Developers //PC 11512477SCurtis.Dunham@arm.com case RIP: return myregs.rip; 11612479SCurtis.Dunham@arm.com //Flags 11712477SCurtis.Dunham@arm.com case EFLAGS: return myregs.eflags; 11812477SCurtis.Dunham@arm.com default: 11912477SCurtis.Dunham@arm.com assert(0); 12012479SCurtis.Dunham@arm.com return 0; 12112477SCurtis.Dunham@arm.com } 12212477SCurtis.Dunham@arm.com} 12312477SCurtis.Dunham@arm.com 12412477SCurtis.Dunham@arm.combool AMD64TraceChild::update(int pid) 12512477SCurtis.Dunham@arm.com{ 12612477SCurtis.Dunham@arm.com oldregs = regs; 12712477SCurtis.Dunham@arm.com if(ptrace(PTRACE_GETREGS, pid, 0, ®s) != 0) 12812478SCurtis.Dunham@arm.com { 12912478SCurtis.Dunham@arm.com cerr << "update: " << strerror(errno) << endl; 13012478SCurtis.Dunham@arm.com return false; 13112478SCurtis.Dunham@arm.com } 13212478SCurtis.Dunham@arm.com for(unsigned int x = 0; x < numregs; x++) 13312478SCurtis.Dunham@arm.com regDiffSinceUpdate[x] = (getRegVal(x) != getOldRegVal(x)); 13412478SCurtis.Dunham@arm.com return true; 13512478SCurtis.Dunham@arm.com} 13612478SCurtis.Dunham@arm.com 13712478SCurtis.Dunham@arm.comAMD64TraceChild::AMD64TraceChild() 13812478SCurtis.Dunham@arm.com{ 13912478SCurtis.Dunham@arm.com for(unsigned int x = 0; x < numregs; x++) 14012478SCurtis.Dunham@arm.com regDiffSinceUpdate[x] = false; 14112478SCurtis.Dunham@arm.com} 14212478SCurtis.Dunham@arm.com 14312478SCurtis.Dunham@arm.comint64_t AMD64TraceChild::getRegVal(int num) 14412479SCurtis.Dunham@arm.com{ 14512479SCurtis.Dunham@arm.com return getRegs(regs, num); 14612479SCurtis.Dunham@arm.com} 14712479SCurtis.Dunham@arm.com 14812479SCurtis.Dunham@arm.comint64_t AMD64TraceChild::getOldRegVal(int num) 14912479SCurtis.Dunham@arm.com{ 15012479SCurtis.Dunham@arm.com return getRegs(oldregs, num); 15112479SCurtis.Dunham@arm.com} 15212479SCurtis.Dunham@arm.com 15312479SCurtis.Dunham@arm.comchar * AMD64TraceChild::printReg(int num) 15412479SCurtis.Dunham@arm.com{ 15512479SCurtis.Dunham@arm.com sprintf(printBuffer, "0x%08X", getRegVal(num)); 15612479SCurtis.Dunham@arm.com return printBuffer; 15712479SCurtis.Dunham@arm.com} 15812479SCurtis.Dunham@arm.com 15912479SCurtis.Dunham@arm.comostream & AMD64TraceChild::outputStartState(ostream & os) 16012479SCurtis.Dunham@arm.com{ 16112479SCurtis.Dunham@arm.com uint64_t sp = getSP(); 16212479SCurtis.Dunham@arm.com uint64_t pc = getPC(); 16312479SCurtis.Dunham@arm.com uint64_t highestInfo = 0; 16412479SCurtis.Dunham@arm.com char obuf[1024]; 16512479SCurtis.Dunham@arm.com sprintf(obuf, "Initial stack pointer = 0x%016llx\n", sp); 16612479SCurtis.Dunham@arm.com os << obuf; 16712479SCurtis.Dunham@arm.com sprintf(obuf, "Initial program counter = 0x%016llx\n", pc); 16812479SCurtis.Dunham@arm.com os << obuf; 16912479SCurtis.Dunham@arm.com 17012479SCurtis.Dunham@arm.com //Output the argument count 17112479SCurtis.Dunham@arm.com uint64_t cargc = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 17212479SCurtis.Dunham@arm.com sprintf(obuf, "0x%016llx: Argc = 0x%016llx\n", sp, cargc); 17312479SCurtis.Dunham@arm.com os << obuf; 17412479SCurtis.Dunham@arm.com sp += 8; 17512479SCurtis.Dunham@arm.com 17612479SCurtis.Dunham@arm.com //Output argv pointers 17712479SCurtis.Dunham@arm.com int argCount = 0; 17812479SCurtis.Dunham@arm.com uint64_t cargv; 17912479SCurtis.Dunham@arm.com do 18012479SCurtis.Dunham@arm.com { 18112479SCurtis.Dunham@arm.com cargv = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 18212479SCurtis.Dunham@arm.com sprintf(obuf, "0x%016llx: argv[%d] = 0x%016llx\n", 18312479SCurtis.Dunham@arm.com sp, argCount++, cargv); 18412479SCurtis.Dunham@arm.com if(cargv) 18512479SCurtis.Dunham@arm.com if(highestInfo < cargv) 18612479SCurtis.Dunham@arm.com highestInfo = cargv; 18712479SCurtis.Dunham@arm.com os << obuf; 18812479SCurtis.Dunham@arm.com sp += 8; 18912479SCurtis.Dunham@arm.com } while(cargv); 19012479SCurtis.Dunham@arm.com 19112479SCurtis.Dunham@arm.com //Output the envp pointers 19212479SCurtis.Dunham@arm.com int envCount = 0; 19312479SCurtis.Dunham@arm.com uint64_t cenvp; 19412479SCurtis.Dunham@arm.com do 19512479SCurtis.Dunham@arm.com { 19612479SCurtis.Dunham@arm.com cenvp = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 19712479SCurtis.Dunham@arm.com sprintf(obuf, "0x%016llx: envp[%d] = 0x%016llx\n", 19812479SCurtis.Dunham@arm.com sp, envCount++, cenvp); 19912479SCurtis.Dunham@arm.com os << obuf; 20012479SCurtis.Dunham@arm.com sp += 8; 20112479SCurtis.Dunham@arm.com } while(cenvp); 20212479SCurtis.Dunham@arm.com uint64_t auxType, auxVal; 20312479SCurtis.Dunham@arm.com do 20412479SCurtis.Dunham@arm.com { 20512479SCurtis.Dunham@arm.com auxType = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 20612479SCurtis.Dunham@arm.com sp += 8; 20712479SCurtis.Dunham@arm.com auxVal = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 20812479SCurtis.Dunham@arm.com sp += 8; 20912479SCurtis.Dunham@arm.com sprintf(obuf, "0x%016llx: Auxiliary vector = {0x%016llx, 0x%016llx}\n", 21012479SCurtis.Dunham@arm.com sp - 16, auxType, auxVal); 21112479SCurtis.Dunham@arm.com os << obuf; 21212479SCurtis.Dunham@arm.com } while(auxType != 0 || auxVal != 0); 21312479SCurtis.Dunham@arm.com //Print out the argument strings, environment strings, and file name. 21412479SCurtis.Dunham@arm.com string current; 21512479SCurtis.Dunham@arm.com uint64_t buf; 21612479SCurtis.Dunham@arm.com uint64_t currentStart = sp; 21712479SCurtis.Dunham@arm.com bool clearedInitialPadding = false; 21812479SCurtis.Dunham@arm.com do 21912479SCurtis.Dunham@arm.com { 22012479SCurtis.Dunham@arm.com buf = ptrace(PTRACE_PEEKDATA, pid, sp, 0); 22112479SCurtis.Dunham@arm.com char * cbuf = (char *)&buf; 22212479SCurtis.Dunham@arm.com for(int x = 0; x < sizeof(uint64_t); x++) 22312479SCurtis.Dunham@arm.com { 22412479SCurtis.Dunham@arm.com if(cbuf[x]) 22512479SCurtis.Dunham@arm.com current += cbuf[x]; 22612479SCurtis.Dunham@arm.com else 22712479SCurtis.Dunham@arm.com { 22812479SCurtis.Dunham@arm.com sprintf(obuf, "0x%016llx: \"%s\"\n", 22912479SCurtis.Dunham@arm.com currentStart, current.c_str()); 23012479SCurtis.Dunham@arm.com os << obuf; 23112479SCurtis.Dunham@arm.com current = ""; 23212479SCurtis.Dunham@arm.com currentStart = sp + x + 1; 23312479SCurtis.Dunham@arm.com } 23412479SCurtis.Dunham@arm.com } 23512479SCurtis.Dunham@arm.com sp += 8; 23612479SCurtis.Dunham@arm.com clearedInitialPadding = clearedInitialPadding || buf != 0; 23712479SCurtis.Dunham@arm.com } while(!clearedInitialPadding || buf != 0 || sp <= highestInfo); 23812479SCurtis.Dunham@arm.com return os; 23912479SCurtis.Dunham@arm.com} 24012479SCurtis.Dunham@arm.com 24112479SCurtis.Dunham@arm.comuint64_t AMD64TraceChild::findSyscall() 24212479SCurtis.Dunham@arm.com{ 24312479SCurtis.Dunham@arm.com uint64_t rip = getPC(); 24412479SCurtis.Dunham@arm.com bool foundOpcode = false; 24512479SCurtis.Dunham@arm.com bool twoByteOpcode = false; 24612479SCurtis.Dunham@arm.com for(;;) 24712479SCurtis.Dunham@arm.com { 24812479SCurtis.Dunham@arm.com uint64_t buf = ptrace(PTRACE_PEEKDATA, pid, rip, 0); 24912479SCurtis.Dunham@arm.com for(int i = 0; i < sizeof(uint64_t); i++) 25012479SCurtis.Dunham@arm.com { 25112479SCurtis.Dunham@arm.com unsigned char byte = buf & 0xFF; 25212479SCurtis.Dunham@arm.com if(!foundOpcode) 25312479SCurtis.Dunham@arm.com { 25412479SCurtis.Dunham@arm.com if(!(byte == 0x66 || //operand override 25512479SCurtis.Dunham@arm.com byte == 0x67 || //address override 25612479SCurtis.Dunham@arm.com byte == 0x2E || //cs 25712479SCurtis.Dunham@arm.com byte == 0x3E || //ds 25812479SCurtis.Dunham@arm.com byte == 0x26 || //es 25912479SCurtis.Dunham@arm.com byte == 0x64 || //fs 26012479SCurtis.Dunham@arm.com byte == 0x65 || //gs 26112479SCurtis.Dunham@arm.com byte == 0x36 || //ss 26212479SCurtis.Dunham@arm.com byte == 0xF0 || //lock 26312479SCurtis.Dunham@arm.com byte == 0xF2 || //repe 26412479SCurtis.Dunham@arm.com byte == 0xF3 || //repne 26512479SCurtis.Dunham@arm.com (byte >= 0x40 && byte <= 0x4F) // REX 26612479SCurtis.Dunham@arm.com )) 26712479SCurtis.Dunham@arm.com { 26812479SCurtis.Dunham@arm.com foundOpcode = true; 26912479SCurtis.Dunham@arm.com } 27012479SCurtis.Dunham@arm.com } 27112479SCurtis.Dunham@arm.com if(foundOpcode) 27212479SCurtis.Dunham@arm.com { 27312479SCurtis.Dunham@arm.com if(twoByteOpcode) 27412479SCurtis.Dunham@arm.com { 27512479SCurtis.Dunham@arm.com //SYSCALL or SYSENTER 27612479SCurtis.Dunham@arm.com if(byte == 0x05 || byte == 0x34) 27712479SCurtis.Dunham@arm.com return rip + 1; 27812479SCurtis.Dunham@arm.com else 27912479SCurtis.Dunham@arm.com return 0; 28012479SCurtis.Dunham@arm.com } 28112479SCurtis.Dunham@arm.com if(!twoByteOpcode) 28212479SCurtis.Dunham@arm.com { 28312479SCurtis.Dunham@arm.com if(byte == 0xCC) // INT3 28412479SCurtis.Dunham@arm.com return rip + 1; 28512479SCurtis.Dunham@arm.com else if(byte == 0xCD) // INT with byte immediate 28612479SCurtis.Dunham@arm.com return rip + 2; 28712479SCurtis.Dunham@arm.com else if(byte == 0x0F) // two byte opcode prefix 28812479SCurtis.Dunham@arm.com twoByteOpcode = true; 28912479SCurtis.Dunham@arm.com else 29012479SCurtis.Dunham@arm.com return 0; 29112479SCurtis.Dunham@arm.com } 29212479SCurtis.Dunham@arm.com } 29312479SCurtis.Dunham@arm.com buf >>= 8; 29412479SCurtis.Dunham@arm.com rip++; 29512479SCurtis.Dunham@arm.com } 29612479SCurtis.Dunham@arm.com } 29712479SCurtis.Dunham@arm.com} 29812479SCurtis.Dunham@arm.com 29912479SCurtis.Dunham@arm.combool AMD64TraceChild::step() 30012479SCurtis.Dunham@arm.com{ 30112479SCurtis.Dunham@arm.com uint64_t ripAfterSyscall = findSyscall(); 30212479SCurtis.Dunham@arm.com if(ripAfterSyscall) 30312479SCurtis.Dunham@arm.com { 30412479SCurtis.Dunham@arm.com //Get the original contents of memory 30512479SCurtis.Dunham@arm.com uint64_t buf = ptrace(PTRACE_PEEKDATA, pid, ripAfterSyscall, 0); 30612479SCurtis.Dunham@arm.com //Patch the first two bytes of the memory immediately after this with 30712479SCurtis.Dunham@arm.com //jmp -2. Either single stepping will take over before this 30812479SCurtis.Dunham@arm.com //instruction, leaving the rip where it should be, or it will take 30912479SCurtis.Dunham@arm.com //over after this instruction, -still- leaving the rip where it should 31012479SCurtis.Dunham@arm.com //be. 31112479SCurtis.Dunham@arm.com uint64_t newBuf = (buf & ~0xFFFF) | 0xFEEB; 31212479SCurtis.Dunham@arm.com //Write the patched memory to the processes address space 31312479SCurtis.Dunham@arm.com ptrace(PTRACE_POKEDATA, pid, ripAfterSyscall, newBuf); 31412479SCurtis.Dunham@arm.com //Step and hit it 31512479SCurtis.Dunham@arm.com ptraceSingleStep(); 31612479SCurtis.Dunham@arm.com //Put things back to the way they started 31712479SCurtis.Dunham@arm.com ptrace(PTRACE_POKEDATA, pid, ripAfterSyscall, buf); 31812479SCurtis.Dunham@arm.com } 31912479SCurtis.Dunham@arm.com else 32012479SCurtis.Dunham@arm.com { 32112479SCurtis.Dunham@arm.com //Get all the way past repe and repne string instructions in one shot. 32212479SCurtis.Dunham@arm.com uint64_t newPC, origPC = getPC(); 32312479SCurtis.Dunham@arm.com do 32412479SCurtis.Dunham@arm.com { 32512479SCurtis.Dunham@arm.com ptraceSingleStep(); 32612479SCurtis.Dunham@arm.com newPC = getPC(); 32712479SCurtis.Dunham@arm.com } while(newPC == origPC); 32812479SCurtis.Dunham@arm.com } 32912479SCurtis.Dunham@arm.com} 33010037SARM gem5 Developers 33110037SARM gem5 DevelopersTraceChild * genTraceChild() 33212477SCurtis.Dunham@arm.com{ 33312479SCurtis.Dunham@arm.com return new AMD64TraceChild; 33412479SCurtis.Dunham@arm.com} 33512477SCurtis.Dunham@arm.com