# [Assembly (MIPS, SPIM)], interpreter bug, 15 bytes main:sb $0 f f: [Try it online!][TIO-la6xvmyb] Here's a cute one I found by accident. This attempts to store a byte to the address at label `f`. This is in the `.text` section. SPIM is pretty clever in that writing out of bounds of a section will just grow the section. However, writing to `.text` is tricky. SPIM stores instructions in a struct for easier parsing, instead of just storing data directly. `CPU/inst.h:57`: ```lang-cpp /* Representation of an instruction. Store the instruction fields in an overlapping manner similar to the real encoding (but not identical, to speed decoding in C code, as opposed to hardware).. */ typedef struct inst_s { short opcode; union { // Snip } r_t; int32 encoding; imm_expr *expr; char *source_line; } instruction; ``` Therefore, to store to `.text`, SPIM will give it a special treatment in `bad_mem_write()`. `CPU/mem.cpp:395`: ```lang-cpp void set_mem_byte(mem_addr addr, reg_word value) { data_modified = true; // .data if ((addr >= DATA_BOT) && (addr < data_top)) data_seg_b [addr - DATA_BOT] = (BYTE_TYPE) value; // .stack else if ((addr >= stack_bot) && (addr < STACK_TOP)) stack_seg_b [addr - stack_bot] = (BYTE_TYPE) value; // .data else if ((addr >= K_DATA_BOT) && (addr < k_data_top)) k_data_seg_b [addr - K_DATA_BOT] = (BYTE_TYPE) value; // .text or section out of bounds else bad_mem_write (addr, value, 0); // <-- } ``` In `bad_mem_write()`, SPIM will to splice and recompile the instruction. `CPU/mem.cpp:506`: ```lang-cpp static void bad_mem_write (mem_addr addr, mem_word value, int mask) { mem_word tmp; if ((addr & mask) != 0) /* Unaligned address fault */ RAISE_EXCEPTION (ExcCode_AdES, CP0_BadVAddr = addr) else if (addr >= TEXT_BOT && addr < text_top) { // For halfword and byte writes, attempt to overwrite part of the instruction. switch (mask) { case 0x0: tmp = ENCODING (text_seg [(addr - TEXT_BOT) >> 2]); #ifdef SPIM_BIGENDIAN tmp = ((tmp & ~(0xff << (8 * (3 - (addr & 0x3))))) | (value & 0xff) << (8 * (3 - (addr & 0x3)))); #else tmp = ((tmp & ~(0xff << (8 * (addr & 0x3)))) | (value & 0xff) << (8 * (addr & 0x3))); #endif break; // ... } // Free instruction if it isn't NULL if (text_seg [(addr - TEXT_BOT) >> 2] != NULL) { free_inst (text_seg[(addr - TEXT_BOT) >> 2]); } // create a new instruction with the encoding text_seg [(addr - TEXT_BOT) >> 2] = inst_decode (tmp); ``` But wait a second.... SPIM checks for NULL... `CPU/mem.cpp:549`: ```lang-c if (text_seg [(addr - TEXT_BOT) >> 2] != NULL) ``` ...after it already dereferenced it! `CPU/mem.cpp:519` (after macro expansion) ```lang-cpp tmp = text_seg [(addr - TEXT_BOT) >> 2]->encoding; ``` And what would cause this instruction to be NULL? If it is not an instruction. And what is at the label `f`? Nothing, so SPIM dereferences a NULL pointer. [Assembly (MIPS, SPIM)]: https://github.com/TryItOnline/spim [TIO-la6xvmyb]: https://tio.run/##Ky7IzP3/PzcxM8@qOElBxUAhjSvN6v9/AA "Assembly (MIPS, SPIM) – Try It Online"