12.11.6. Accessing registers and memory in debug state

This section describes the following:

Reading and writing registers through the DCC

To read a single register, the debugger can use the sequence shown in Example 12.13. This sequence depends on two other sequences, Example 12.1 and Example 12.4.

Example 12.13. Reading an ARM register

uint32 ReadARMRegister(int Rd)
{
    // Step 1. Execute instruction MCR p14, 0, Rd, c0, c5, 0 through the ITR.
    ExecuteARMInstruction(0xEE000E15 + (Rd<<12));
    // Step 2. Read the register value through DTRTX.
    reg_val := ReadDCC();
    return reg_val;
}

Example 12.14 shows a similar sequence for writing an ARM register.

Example 12.14. Writing an ARM register

WriteRegister(int Rd, uint32 reg_val)
{
    // Step 1. Write the register value to DTRRX.
    WriteDCC(reg_val);
    // Step 2. Execute instruction MRC p14, 0, Rd, c0, c5, 0 to the ITR.
    ExecuteARMInstruction(0xEE100E15 + (Rd<<12));
}

Reading the PC in debug state

Example 12.15 shows the code to read the PC.

Note

You can use a similar sequence to write to the PC to set the return address when leaving debug state.

Example 12.15. Reading the PC

ReadPC()
{
    // Step 1. Save R0
    saved_r0 := ReadRegister(0);
    // Step 2. Execute the instruction MOV r0, pc through the ITR.
    ExecuteARMInstruction(0xE1A0000F);
    // Step 3. Read the value of r0 that now contains the PC.
    pc := ReadRegister(0);
    // Step 4. Restore the value of R0.
    WriteRegister(0, saved_r0);
    return pc;
}

Reading the CPSR in debug state

Example 12.16 shows the code for reading the CPSR.

Example 12.16. Reading the CPSR

ReadCPSR()
{
    // Step 1. Save R0.
    saved_r0 := ReadRegister(0);
    // Step 2. Execute instruction MRS r0, CPSR through the ITR.
    ExecuteARMInstruction(0xE10F0000);
    // Step 3. Read the value of r0 that now contains the CPSR
    cpsr_val := ReadRegister(0);
    // Step 4. Restore the value of R0.
    WriteRegister(0, saved_r0);
    return cpsr_val;
}

Note

You can use similar sequences to read the SPSR in privileged modes.

Writing the CPSR in debug state

Example 12.17 shows the code for writing the CPSR.

Example 12.17. Writing the CPSR

WriteCPSR(uint32 cpsr_val)
{
    // Step 1. Save R0.
    saved_r0 := ReadRegister(0);
    // Step 2. Write the new CPSR value to r0.
    WriteRegister(0, cpsr_val);
    // Step 3. Execute instruction MSR r0, CPSR through the ITR.
    ExecuteARMInstruction(0xE12FF000);
    // Step 4. Execute a PrefetchFlush instruction through the ITR.
    ExecuteARMInstruction(9xEE070F95);
    // Step 5. Restore the value of r0.
    WriteRegister(0, saved_r0);
}

Reading memory

Example 12.18 shows the code for reading a byte of memory.

Example 12.18. Reading a byte of memory

uint8 ReadByte(uint32 address, bool &aborted)
{
    // Step 1. Save the values of r0 and r1.
    saved_r0 := ReadRegister(0);
    saved_r1 := ReadRegister(1);
    // Step 2. Write the address to r0.
    WriteRegister(0, address);
    // Step 3. Execute the instruction LDRB r1,[r0] through the ITR.
    ExecuteARMInstruction(0xE5D01000);
    // Step 4. Read the value of r1 that contains the data at the address.
    datum := ReadRegister(1);
    // Step 5. Restore the corrupted registers r0 and r1.
    WriteRegister(0, saved_r0);
    WriteRegister(1, saved_r1);
    // Step 6. Check the DSCR for a sticky abort.
    aborted := CheckForAborts();
    return datum;
}

Example 12.19 shows the code for checking for aborts after a memory access.

Example 12.19. Checking for an abort after memory access

bool CheckForAborts()
{
    // Step 1. Check the DSCR for a sticky abort.
    dscr := ReadDebugRegister(34);
    if (dscr & ((1<<6) + (1<<7))
    {
        // Step 2. Clear the sticky flag by writing DRCR[2].
        WriteDebugRegister(36, 1<<2);
        return true;
    }
    else
    {
        return false;
    }
}

Note

You can use similar sequence to read half-word of memory and to write to memory.

To read or write blocks of memory, substitute the data instruction with one that uses post-indexed addressing. For example:

LDRB r1, [r0],#1

This is done to prevent reloading the address value for each sequential word.

Example 12.20 shows the code for reading a block of bytes of memory.

Example 12.20. Reading a block of bytes of memory

ReadBytes(uint32 address, bool &aborted, uint8 *data, int nbytes)
{
    // Step 1. Save the value of r0 and r1.
    saved_r0 := ReadRegister(0);
    saved_r1 := ReadRegister(1);
    // Step 2. Write the address to r0
    WriteRegister(0, address);
    while (nbytes > 0)
    {
        // Step 3. Execute instruction LDRB r1,[r0],1 through the ITR.
        ExecuteARMInstruction(0xE4D01001);
            // Step 4. Read the value of r1 that contains the data at the
            // address.
        *data++ := ReadRegister(1);
        --nbytes;
    }
    // Step 5. Restore the corrupted registers r0 and r1.
    WriteRegister(0, saved_r0);
    WriteRegister(1, saved-r1);
    // Step 6. Check the DSCR for a sticky abort.
    aborted := CheckForAborts();
    return datum;
}

Example 12.21 shows the sequence for reading a word of memory.

Note

A faster method is available for reading and writing words using the direct memory access function of the DCC. See Fast memory read/write.

Example 12.21. Reading a word of memory

uint32 ReadWord(uint32 address, bool &aborted)
{
    // Step 1. Save the value of r0.
    saved_r0 := ReadRegister(0);
    // Step 2. Write the address to r0.
    WriteRegister(0, address);
    // Step 3. Execute instruction STC p14, c5, [r0] through the ITR.
    ExecuteARMInstruction(0xED905E00);
    // Step 4. Read the value from the DTR directly.
    datum := ReadDCC();
    // Step 5. Restore the corrupted register r0.
    WriteRegister(0, saved_r0);
    // Step 6. Check the DSCR for a sticky abort.
    aborted := CheckForAborts();
    return datum;
}

Fast register read/write

When multiple registers must be read in succession, you can optimize the process by placing the DCC into stall mode and by writing the value 1 to the DCC access mode bits. For more information, see CP14 c1, Debug Status and Control Register.

Example 12.22 shows the sequence to change the DTR access mode.

Example 12.22. Changing the DTR access mode

SetDTRAccessMode(int mode)
{
    // Step 1. Write the mode value to DSCR[21:20].
    dscr := ReadDebugRegister(34);
    dscr := (dscr & ~(0x3<<20)) | (mode<<20);
    WriteDebugRegister(34, dscr);
}

Example 12.23 shows the sequence to read registers in stall mode.

Example 12.23. Reading registers in stall mode

ReadRegisterStallMode(int Rd)
{
    // Step 1. Write the opcode for MCR p14, 0, Rd, c5, c0 to the ITR.
    // Write stalls until the ITR is ready.
    WriteDebugRegister(33, 0xEE000E15 + (Rd<<12));
    // Step 2. Read the register value through the DCC. Read stalls until 
    // DTRTX is ready
    reg_val := ReadDebugRegister(32);
    return reg_val;
}

Example 12.24 shows the sequence to write registers in stall mode.

Example 12.24. Writing registers in stall mode

WriteRegisterInStallMode(int Rd, uint32 value)
{
    // Step 1. Write the value to the DTRRX.
    // Write stalls until the DTRRX is ready.
    WriteDebugRegister(35, value);
    // Step 2. Write the opcode for MRC p14, 0, Rd, c5, c0 to the ITR.
    // Write stalls until the ITR is ready.
    WriteDebugRegister(33, 0xEE100E15 + (Rd<<12));
}

Note

To transfer a register to the processor when in stall mode, you are not required to poll the DSCR each time an instruction is written to the ITR and a value read from or written to the DTR. The processor stalls using the signal PREADY until the previous instruction has completed or the DTR register is ready for the operation.

Fast memory read/write

This section provides example code that enable faster reads from memory by making use of the DTR access mode.

Example 12.25 shows the sequence for reading a block of words of memory.

Example 12.25. Reading a block of words of memory

ReadWords(uint32 address, bool &aborted, uint32 *data, int nwords)
{
    // Step 1. Write the value b01 to DSCR[21:20] for stall mode.
    SetDTRAccessMode(2'b01);
    // Step 2. Save the value of r0.
    saved_r0 := ReadRegisterInStallMode(0);
    // Step 3. Write the address to read from to the DTRRX.
    // Write stalls until the DTRRX is ready.
    WriteRegisterInStallMode(0, address);
    // Step 4. Write the opcode for LDC p14, c5, [r0], 4 to the ITR.
    // Write stalls until the ITR is ready.
    WriteDebugRegister(33, 0xED903E00));
    // Step 5. Write the value 2'b10 to DSCR[21:20] for fast mode.
    SetDCCAccessMode(2);
    // Step 6. Loop reading out the data.
    // Each time a word is read, the instruction is reissued.
    while (nwords > 1)
    {
        *data++ = ReadDebugRegister(32);
        --nwords;
    }
    // Step 7. Write the value 2'b00 to DSCR[21:20] for normal mode.
    SetDTRAccessMode(2’b00);
    // Step 8. Read the last word from the DTR.
    // The DSCR[29] check may timeout if a data abort occurred while reading
    // memory.    dscr := ReadDebugRegister(34);    timeout = 0;    until ((dscr & (1<<29)) || (timeout == TIMEOUT)) 
    {        dscr := ReadDebugRegister(34);        ++timeout;
    }
    // Step 9. Restore the corrupted register r0.
    WriteRegisterInStallMode(0, saved_r0);
    // Step 10. Check the DSCR for a sticky abort.
    aborted := CheckForAborts();
}

Example 12.26 shows the sequence for writing a block of words to memory.

Example 12.26. Writing a block of words to memory (fast download)

WriteWords(uint32 address, bool &aborted, uint32 *data, int nwords)
{
    // Step 1. Save the value of r0.
    saved_r0 := ReadRegister(0);
    // Step 2. Write the value 2'b10 to DSCR[21:20] for fast mode.
    SetDTRAccessMode(2b10);
    // Step 3. Write the opcode for MCR p14, 0, r0, c5, c0 to the ITR.
    // Write stalls until the ITR is ready but the instruction is not issued.
    WriteDebugRegister(33, 0xEE000E15);
    // Step 4. Write the address to read from to the DTRRX
    // Write stalls until the ITR is ready, but the instruction is not reissued.
    WriteDebugRegister(33, address);
    // Step 5. Write the opcode for STC p14, c5, [r0], 4 to the ITR.
    // Write stalls until the ITR is ready but the instruction is not issued.
    WriteDebugRegister(33, 0xED803E00);
    // Step 6. Loop writing the data.
    // Each time a word is written to the DTR, the instruction is reissued.
    while (nwords > 0)
    {
        WriteDebugRegister(33, *data++);
        --nwords;
    }
    // Step 7. Write the value 2'b00 to DSCR[21:20] for normal mode.
    SetDTRAccessMode(2'b00);
    // Step 8. Restore the corrupted register r0.
    WriteRegister(0, saved_r0);
    // Step 9. Check the DSCR for a sticky abort.
    aborted := CheckForAborts();
}

Note

As the amount of data transferred increases, these functions reach an optimum performance of one debug register access per data word transferred.

Accessing secure and nonsecure coprocessor registers

The sequence for accessing coprocessor registers is the same as for the PC and CPSR. That is, you must first execute an instruction to transfer the register to an ARM register, then read the value back through the DTR.

Example 12.27 shows the sequence for reading a coprocessor register.

Example 12.27. Reading a coprocessor register

uint32 ReadCPReg(int CPnum, int opc1, int CRn, int CRm, int opc2)
{
    // Step 1. Save R0.
    saved_r0 := ReadRegister(0);
    // Step 2. Execute instruction MCR p15, 0, r0, c0, c1, 0 through the ITR.
    ExecuteARMInstruction(0xEE000010 + (CPnum<<8) + (opc1<<21) + (CRn<<16) + CRm + (opc2<<5));
    // Step 3. Read the value of r0 that now contains the CP register.
    CP15c1 := ReadRegister(0);
    // Step 4. Restore the value of R0.
    WriteRegister(0, saved_r0);
    return CP15c1;
}

Note

For banked CP15 registers, it might be necessary to switch security state to read the required coprocessor register. Switching from secure to nonsecure state is simple because you can write to the Secure Configuration Register (SCR) from any secure privileged mode.

Example 12.28 shows the sequence for changing from secure to nonsecure state.

Example 12.28. Changing from secure to nonsecure state

SecureToNonSecure()
{
    // Step 1. Set the NS bit.
    scr := ReadCPReg(15, 0, 1, 1, 0);
    scr := (scr | 1);
    WriteCPReg(15, 0, 1, 1, 0, scr);
}

Example 12.29 shows the sequence to change from nonsecure to secure state, however the processor must first be in Monitor mode.

Example 12.29. Changing from nonsecure to secure state

NonSecureToSecure()
{
    // Step 1. Change the processor to Monitor mode.
    saved_cpsr := ReadCPSR();
    new_cpsr := (saved_cpsr & ~0x1F) | 0x16;
    WriteCPSR(new_cpsr);
    // Step 2. Clear the NS bit.
    scr := ReadCPReg(15, 0, 1, 1, 0);
    scr := (scr & ~1);
    WriteCPReg(15, 0, 1, 1, 0, scr);
    // Step 3. Restore the processor mode.
    WriteCPSR(saved_cpsr);
}

Copyright © 2006-2009 ARM Limited. All rights reserved.ARM DDI 0344I
Non-Confidential