Msg/FetchSelfModifyingCode

From CPUlator Wiki

< Msg
Revision as of 06:55, 11 March 2019 by Henry (talk | contribs) (Created page with "Normally, the CPU should execute code that came from an executable file generated by an assembler or compiler. Normally, this code shouldn't be changed at runtime by directly...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Normally, the CPU should execute code that came from an executable file generated by an assembler or compiler. Normally, this code shouldn't be changed at runtime by directly modifying the instructions in memory. A program that (intentionally or unintentionally) modifies its own instructions and then executes them is called self-modifying code. This warning tells you that the instruction being executed came from an executable, and that the instruction is now different that what was in the executable.

There are some cases where intentional self-modifying code is useful. But unintentional self-modifying code is almost certainly incorrect.

Examples

ARMv7

.global _start
_start:
	ldr r0, =0xe2822001		// An opcode
    str r0, [pc, #0]		// Modify instruction at address 0x10. Click "Refresh" to see the new instruction.
	nop
    nop						// This instruction gets changed to an add (opcode 0xe2822001)
	nop

Nios II

.global _start
_start:
	movia r2, 0x18c00044	# An opcode
    stw r2, 0x10(r0)		# Modify instruction at address 0x10. Click "Refresh" to see the new instruction.
	nop
    nop					# This instruction gets changed to an add (opcode 0x18c00044)
	nop

On the simulator, the highlighted instruction executes as an add (r3 gets incremented), and not the original nop, because the opcode was changed in memory before execution. On real hardware, the behaviour is typically undefined (you may execute the old or new instruction) until you've flushed the instruction cache and flushed the instruction pipeline.

Debugging

Unintended self modifying code is notoriously hard to debug. The modified code is not detected until it is next executed, and it may have been modified a very long time ago, so it is often not easy to find the part of the program that actually did the modification. The main objective is to find which part of the program stored values into memory space used by your program's instructions.

  • A common cause of this problem is a memory store somewhere in your program that uses a bad pointer value. This could be simply computing the pointer incorrectly, or overrunning an array bound and overwriting a large swath of memory. Look at the value of the new "opcode" and see if it matches any data pattern that you might have been writing.
  • Use a data watchpoint (Watchpoints window, located in the same panel as the registers window by default). A watchpoint can interrupt the program whenever a memory load or store occurs to a range of addresses. Use a watchpoint to look for memory writes to the range of addresses used by your code (set the start and end addresses), and set Pause on Write. Then reload and restart your program. The watchpoint will stop your program whenever a store occurs to your selected region (your instructions), which will hopefully get you much closer to finding the root cause of the problem.

Disabling this message

This debugging check can be disabled in the Debugging Checks section of the Settings box: Instruction fetch: Modified opcode.