I’m grateful to Github for many things, but especially for making source control usage “normal” behavior. Perhaps Github’s popularity correlated with, rather than caused this trend, but I’m going to give them this one. Since Github’s advent, I’ve not needed to remind clients and new hires to put code into source control–it has become second nature. As a result, I have not had to patch a binary for quite some time now. There was once a time when the phrase, “I’ll send you the source code,” found me in a cold sweat, hitting “check mail” in Outlook until the promised zip arrived. I’d be crushed to discover that the zip contained my worst fear: a binary executable, and nothing more. Invariably, I’d receive the, “oh, that’s all I have,” response to my grasping follow-up–hoping they might scrape together some crusty scraps of actual source out of old emails or off of that forgotten shared folder. After several of these experiences, I decided that it might be worth trying (or at least fun to try) to treat the binary as the source code that everyone seemed to think it was. I invested in a personal license of IDA Pro, honed up on assembly, and dove into the wonderful, crazy world of binary reverse-engineering and patching. It was dull, slow, and painful at first, but as I practiced, I discovered that I could see patterns that sped up my work. Before long, I could actually make useful modifications to binary executables–changing behaviors and adding small features. I also found myself developing a much deeper and intuitive sense for memory maps, pointer math, and data structures that translated to the work I did in C. Seeing how my machines work at a low-level was helping me instruct them at a higher level.

I can’t recommend that modern developers follow the same, arcane path I did. But I will suggest that it’s instructional and fun to tinker with assembly on occasion if you work in a systems programming language such as C or C++. I’d doubly suggest this if you’re doing embedded work. In that spirit, I figured I’d point out a couple of common patterns with which you might familiarize yourself. I like ARM, so I’m using an ARM cross compiler that you can download for Mac OS, Linux, and Windows (see notes at the end). I’ve significantly simplified the following disassembly listings to make them easier to read. However, you can check out the raw listings by compiling the examples located at this Github repository: https://github.com/MindTribe/DisassemblyPatterns. Finally, I’ve commented most of the listings, but don’t expect this to be a thorough assembly tutorial. You might care to skim through an ARM instruction set reference (PDF). Let’s begin by looking at a C if statement:

static char const * get_string ( int stringNum ) { if ( stringNum == 1 ) { return "Bitz" ; } } static char const *get_string(int stringNum) { if (stringNum == 1) { return "Bitz"; } }

The above checks to see if stringNum is 1 and, if so, it returns the string “Bitz”. My compiler turns it into the following assembly:

; static char const *get_string(int stringNum) { sub sp , sp , # 8 ; Prepare stack pointer str r0 , [ sp , # 4 ] ; store stringNum argument on the stack for later use ; if (stringNum == 1) { ldr r3 , [ sp , # 4 ] ; load stringNum from the stack cmp r3 , # 1 ; Compare stringNum with 1 bne . L6 ; If stringNum is not 1, jump to L6 (end of function) ; return "Bitz"; ldr r3 , . L8 ; Load string pointed to by word at L8 ("Bitz") b . L5 ; Jump to L5 (end of function) . L6 : . L5 : ; } ; } mov r0 , r3 ; Move the loaded string address into r0 ; (r0 holds return values by convention) add sp , sp , # 8 ; clean up stack bx lr ; return ; Table of pointers to strings: . L8 : . word . LC1 ; address of "Bitz" ; String constants: . LC1 : . ascii "Bitz00" ; static char const *get_string(int stringNum) { sub sp, sp, #8 ; Prepare stack pointer str r0, [sp, #4] ; store stringNum argument on the stack for later use ; if (stringNum == 1) { ldr r3, [sp, #4] ; load stringNum from the stack cmp r3, #1 ; Compare stringNum with 1 bne .L6 ; If stringNum is not 1, jump to L6 (end of function) ; return "Bitz"; ldr r3, .L8 ; Load string pointed to by word at L8 ("Bitz") b .L5 ; Jump to L5 (end of function) .L6: .L5: ; } ; } mov r0, r3 ; Move the loaded string address into r0 ; (r0 holds return values by convention) add sp, sp, #8 ; clean up stack bx lr ; return ; Table of pointers to strings: .L8: .word .LC1 ; address of "Bitz" ; String constants: .LC1: .ascii "Bitz00"

Notice that the C if statement boils down to a compare and a jump in assembly. When you see a compare and jump together, you can safely think of them as an if statement. However, it can take a moment to read and understand the if logic. The instructions above seem to negate the C conditional logic by taking action if the C if statement evaluates to false–in this case, jumping to the end of the function. This can look backwards, considering that the C statement implies an action taken if the conditional evaluates to true. However, both accomplish the same thing (if they don’t, my compiler is broken).

[Sidebar: There’s some strange indirection that loads the address of LC1 by declaring it as a word at L8. The compiler takes this shortcut to account for the fact that the address of LC1 most likely won’t fit into a single mov instruction. Therefore, instead of using multiple instructions to get the address of LC1 into r3, it simply puts the address into a word nearby and loads that. I’m sure that’s not a satisfactory explanation for many, so I’d encourage you to read through the ARM mov and ldr instructions to better understand this.

You might have also noticed the unnecessary stack store and load at the beginning of the function. This is a gentle introduction to assembly patterns, so I’ve compiled with optimizations turned off to make the assembly easier to follow–but you’ll see inefficiencies such as the superfluous stack operation.]

The C code above has a serious bug: If stringNum is not equal to 1, the return value of get_string() is undefined. In fact, if stringNum is not equal to 1, the current implementation will return the value passed as stringNum as though it were the address of a string–simply due to the way the register assignment worked out. That’s bad. Let’s fix it by adding an else clause:

static char const * get_string ( int stringNum ) { if ( stringNum == 1 ) { return "Bitz" ; } else { return "Nothing" ; } } static char const *get_string(int stringNum) { if (stringNum == 1) { return "Bitz"; } else { return "Nothing"; } }

This disassembles to:

; static char const *get_string(int stringNum) { sub sp , sp , # 8 ; Prepare stack pointer str r0 , [ sp , # 4 ] ; store stringNum argument on the stack for later use ; if (stringNum == 1) { ldr r3 , [ sp , # 4 ] ; load stringNum from the stack cmp r3 , # 1 ; Compare stringNum with 1 bne . L6 ; If stringNum is not 1, jump to L6 ; return "Bitz"; ldr r3 , . L8 ; Load string pointed to by word at L8 ("Bitz") b . L7 ; Jump to L7 (end of function) . L6 : ; } else { ; return "Nothing"; ldr r3 , . L8 + 4 ; Load string pointed to by word at L8+4 ("Nothing") . L7 : ; } ; } mov r0 , r3 ; Move the loaded string address into r0 add sp , sp , # 8 ; clean up stack bx lr ; return ; Table of pointers to strings: . L8 : . word . LC1 ; address of "Bitz" . word . LC2 ; address of "Nothing" ; String constants: . LC1 : . ascii "Bitz00" . LC2 : . ascii "Nothing00" ; static char const *get_string(int stringNum) { sub sp, sp, #8 ; Prepare stack pointer str r0, [sp, #4] ; store stringNum argument on the stack for later use ; if (stringNum == 1) { ldr r3, [sp, #4] ; load stringNum from the stack cmp r3, #1 ; Compare stringNum with 1 bne .L6 ; If stringNum is not 1, jump to L6 ; return "Bitz"; ldr r3, .L8 ; Load string pointed to by word at L8 ("Bitz") b .L7 ; Jump to L7 (end of function) .L6: ; } else { ; return "Nothing"; ldr r3, .L8+4 ; Load string pointed to by word at L8+4 ("Nothing") .L7: ; } ; } mov r0, r3 ; Move the loaded string address into r0 add sp, sp, #8 ; clean up stack bx lr ; return ; Table of pointers to strings: .L8: .word .LC1 ; address of "Bitz" .word .LC2 ; address of "Nothing" ; String constants: .LC1: .ascii "Bitz00" .LC2: .ascii "Nothing00"

Notice that the if statement still boils down to a compare and a jump. But now, instead of jumping to the end of the function if the conditional is not true, it jumps to the else block. The else block then loads the default string and falls through to the end of the function. Now let’s look at a small C switch statement:

static char const * get_string ( int stringNum ) { switch ( stringNum ) { case 0 : return "Bitz" ; case 1 : return "Bytez" ; case 42 : return "Answerz" ; default : return "Nothing" ; } } static char const *get_string(int stringNum) { switch (stringNum) { case 0: return "Bitz"; case 1: return "Bytez"; case 42: return "Answerz"; default: return "Nothing"; } }

This disassembles roughly into the following:

; static char const *get_string(int stringNum) { sub sp , sp , # 8 ; Prepare stack pointer str r0 , [ sp , # 4 ] ; store stringNum argument on the stack for later use ; switch (stringNum) { ldr r3 , [ sp , # 4 ] ; load stringNum from the stack cmp r3 , # 1 ; Compare stringNum with 1 beq . L8 ; If stringNum is 1, jump to L8 cmp r3 , # 42 ; Compare stringNum with 42 beq . L9 ; If stringNum is 42, jump to L9 cmp r3 , # 0 ; Compare stringNum with 0 bne . L11 ; If stringNum is NOT 0, jump to L11 . L7 : ; case 0: ; return "Bitz"; ldr r3 , . L12 ; Load string pointed to by word at L12 ("Bitz") b . L10 ; Jump to L10 (end of function) . L8 : ; case 1: ; return "Bytez"; ldr r3 , . L12 + 4 ; Load string pointed to by word at L12+4 ("Bytez") b . L10 ; Jump to L10 (end of function) . L9 : ; case 42: ; return "Answerz"; ldr r3 , . L12 + 8 ; Load string pointed to by word at L12+8 ("Answerz") b . L10 ; Jump to L10 (end of function) . L11 : ; default: ; return "Nothing"; ldr r3 , . L12 + 12 ; Load string pointed to by word at L12+12 ("Nothing") . L10 : ; } ; } mov r0 , r3 ; Move the loaded string address into r0 add sp , sp , # 8 ; clean up stack bx lr ; return ; Table of pointers to strings: . L12 : . word . LC1 ; address of "Bitz" . word . LC2 ; address of "Bytez" . word . LC3 ; address of "Answerz" . word . LC4 ; address of "Nothing" ; String constants: . LC1 : . ascii "Bitz00" . LC2 : . ascii "Bytez00" . LC3 : . ascii "Answerz00" . LC4 : . ascii "Nothing00" ; static char const *get_string(int stringNum) { sub sp, sp, #8 ; Prepare stack pointer str r0, [sp, #4] ; store stringNum argument on the stack for later use ; switch (stringNum) { ldr r3, [sp, #4] ; load stringNum from the stack cmp r3, #1 ; Compare stringNum with 1 beq .L8 ; If stringNum is 1, jump to L8 cmp r3, #42 ; Compare stringNum with 42 beq .L9 ; If stringNum is 42, jump to L9 cmp r3, #0 ; Compare stringNum with 0 bne .L11 ; If stringNum is NOT 0, jump to L11 .L7: ; case 0: ; return "Bitz"; ldr r3, .L12 ; Load string pointed to by word at L12 ("Bitz") b .L10 ; Jump to L10 (end of function) .L8: ; case 1: ; return "Bytez"; ldr r3, .L12+4 ; Load string pointed to by word at L12+4 ("Bytez") b .L10 ; Jump to L10 (end of function) .L9: ; case 42: ; return "Answerz"; ldr r3, .L12+8 ; Load string pointed to by word at L12+8 ("Answerz") b .L10 ; Jump to L10 (end of function) .L11: ; default: ; return "Nothing"; ldr r3, .L12+12 ; Load string pointed to by word at L12+12 ("Nothing") .L10: ; } ; } mov r0, r3 ; Move the loaded string address into r0 add sp, sp, #8 ; clean up stack bx lr ; return ; Table of pointers to strings: .L12: .word .LC1 ; address of "Bitz" .word .LC2 ; address of "Bytez" .word .LC3 ; address of "Answerz" .word .LC4 ; address of "Nothing" ; String constants: .LC1: .ascii "Bitz00" .LC2: .ascii "Bytez00" .LC3: .ascii "Answerz00" .LC4: .ascii "Nothing00"

Check that out. It looks like a big if-else statement. We probably could have written it like this with the same results:

static char const * get_string ( int stringNum ) { if ( stringNum == 1 ) { return "Bytez" ; } else if ( stringNum == 42 ) { return "Answerz" ; } else if ( stringNum == 0 ) { return "Bitz" ; } else { return "Nothing" ; } } static char const *get_string(int stringNum) { if (stringNum == 1) { return "Bytez"; } else if (stringNum == 42) { return "Answerz"; } else if (stringNum == 0) { return "Bitz"; } else { return "Nothing"; } }

Note especially that the first case (case 0) is evaluated last in that chain of conditionals. This means that the function will take longer to run when you call it with 0. Should you worry about this? NO. NONONONONO! Well, not bloody likely. In the overwhelming majority of cases, worrying about generated code is premature optimization. Remember, this is an exercise in understanding how your C code translates to assembly for the purposes of understanding and debugging–not for optimization. With that in mind, simply note with mild interest that the compiler is not generating code that is time-constant for all cases and then move along. Let’s try a larger switch:

static char const * get_string ( int stringNum ) { switch ( stringNum ) { case 0 : return "Bitz" ; case 1 : return "Bytez" ; case 2 : return "Wordz" ; case 3 : return "Fancywordz" ; case 4 : return "Longz" ; case 42 : return "Answerz" ; default : return "Nothing" ; } } static char const *get_string(int stringNum) { switch (stringNum) { case 0: return "Bitz"; case 1: return "Bytez"; case 2: return "Wordz"; case 3: return "Fancywordz"; case 4: return "Longz"; case 42: return "Answerz"; default: return "Nothing"; } }

; static char const *get_string(int stringNum) { sub sp , sp , # 8 ; Prepare stack pointer str r0 , [ sp , # 4 ] ; store stringNum argument on the stack for later use ; switch (stringNum) { ldr r3 , [ sp , # 4 ] ; load stringNum from the stack cmp r3 , # 42 ; Compare stringNum with 42 ldrls pc , [ pc , r3 , asl # 2 ] ; If stringNum <= 42 load the program counter ; with an address from following table--as found ; by adding stringNum*2 to the current value of ; the program counter. This effectively jumps ; to one of the blocks of code that follow the ; table. b . L6 ; If stringNum was > 42, jump to L6 (default case) . L13 : . word . L7 ; Address of code block for case 0 . word . L8 ; Address of code block for case 1 . word . L9 ; Address of code block for case 2 . word . L10 ; Address of code block for case 3 . word . L11 ; Address of code block for case 4 . word . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 ; Address of code block for . word . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 ; default case, repeated 37 . word . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 ; times . word . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 , . L6 ; . word . L6 , . L6 , . L6 , . L6 , . L6 ; . word . L12 ; Address of code block for case 42 . L7 : ; case 0: ; return "Bitz"; ldr r3 , . L15 ; Load string pointed to by word at L15 ("Bitz") b . L14 ; Jump to L14 (end of function) . L8 : ; case 1: ; return "Bytez"; ldr r3 , . L15 + 4 ; Load string pointed to by word at L15+4 ("Bytez") b . L14 ; Jump to L14 (end of function) . L9 : ; case 2: ; return "Wordz"; ldr r3 , . L15 + 8 ; Load string pointed to by word at L15+8 ("Wordz") b . L14 ; Jump to L14 (end of function) . L10 : ; case 3: ; return "Fancywordz"; ldr r3 , . L15 + 12 ; Load string pointed to by word at L15+12 ("Fancywordz") b . L14 ; Jump to L14 (end of function) . L11 : ; case 4: ; return "Longz"; ldr r3 , . L15 + 16 ; Load string pointed to by word at L15+16 ("Longz") b . L14 ; Jump to L14 (end of function) . L12 : ; case 42: ; return "Answerz"; ldr r3 , . L15 + 20 ; Load string pointed to by word at L15+20 ("Answerz") b . L14 ; Jump to L14 (end of function) . L6 : ; default: ; return "Nothing"; ldr r3 , . L15 + 24 ; Load string pointed to by word at L15+24 ("Nothing") . L14 : ; } ; } mov r0 , r3 ; Move the loaded string address into r0 add sp , sp , # 8 ; clean up stack bx lr ; return ; Table of pointers to strings: . L15 : . word . LC1 ; address of "Bitz" . word . LC2 ; address of "Bytez" . word . LC3 ; address of "Wordz" . word . LC4 ; address of "Fancywordz" . word . LC5 ; address of "Longz" . word . LC6 ; address of "Answerz" . word . LC7 ; address of "Nothing" ; String constants: . LC1 : . ascii "Bitz00" . LC2 : . ascii "Bytez00" . LC3 : . ascii "Wordz00" . LC4 : . ascii "Fancywordz00" . LC5 : . ascii "Longz00" . LC6 : . ascii "Answerz00" . LC7 : . ascii "Nothing00" ; static char const *get_string(int stringNum) { sub sp, sp, #8 ; Prepare stack pointer str r0, [sp, #4] ; store stringNum argument on the stack for later use ; switch (stringNum) { ldr r3, [sp, #4] ; load stringNum from the stack cmp r3, #42 ; Compare stringNum with 42 ldrls pc, [pc, r3, asl #2] ; If stringNum <= 42 load the program counter ; with an address from following table--as found ; by adding stringNum*2 to the current value of ; the program counter. This effectively jumps ; to one of the blocks of code that follow the ; table. b .L6 ; If stringNum was > 42, jump to L6 (default case) .L13: .word .L7 ; Address of code block for case 0 .word .L8 ; Address of code block for case 1 .word .L9 ; Address of code block for case 2 .word .L10 ; Address of code block for case 3 .word .L11 ; Address of code block for case 4 .word .L6, .L6, .L6, .L6, .L6, .L6, .L6, .L6 ; Address of code block for .word .L6, .L6, .L6, .L6, .L6, .L6, .L6, .L6 ; default case, repeated 37 .word .L6, .L6, .L6, .L6, .L6, .L6, .L6, .L6 ; times .word .L6, .L6, .L6, .L6, .L6, .L6, .L6, .L6 ; .word .L6, .L6, .L6, .L6, .L6 ; .word .L12 ; Address of code block for case 42 .L7: ; case 0: ; return "Bitz"; ldr r3, .L15 ; Load string pointed to by word at L15 ("Bitz") b .L14 ; Jump to L14 (end of function) .L8: ; case 1: ; return "Bytez"; ldr r3, .L15+4 ; Load string pointed to by word at L15+4 ("Bytez") b .L14 ; Jump to L14 (end of function) .L9: ; case 2: ; return "Wordz"; ldr r3, .L15+8 ; Load string pointed to by word at L15+8 ("Wordz") b .L14 ; Jump to L14 (end of function) .L10: ; case 3: ; return "Fancywordz"; ldr r3, .L15+12 ; Load string pointed to by word at L15+12 ("Fancywordz") b .L14 ; Jump to L14 (end of function) .L11: ; case 4: ; return "Longz"; ldr r3, .L15+16 ; Load string pointed to by word at L15+16 ("Longz") b .L14 ; Jump to L14 (end of function) .L12: ; case 42: ; return "Answerz"; ldr r3, .L15+20 ; Load string pointed to by word at L15+20 ("Answerz") b .L14 ; Jump to L14 (end of function) .L6: ; default: ; return "Nothing"; ldr r3, .L15+24 ; Load string pointed to by word at L15+24 ("Nothing") .L14: ; } ; } mov r0, r3 ; Move the loaded string address into r0 add sp, sp, #8 ; clean up stack bx lr ; return ; Table of pointers to strings: .L15: .word .LC1 ; address of "Bitz" .word .LC2 ; address of "Bytez" .word .LC3 ; address of "Wordz" .word .LC4 ; address of "Fancywordz" .word .LC5 ; address of "Longz" .word .LC6 ; address of "Answerz" .word .LC7 ; address of "Nothing" ; String constants: .LC1: .ascii "Bitz00" .LC2: .ascii "Bytez00" .LC3: .ascii "Wordz00" .LC4: .ascii "Fancywordz00" .LC5: .ascii "Longz00" .LC6: .ascii "Answerz00" .LC7: .ascii "Nothing00"

Cool. We got the compiler to change its strategy. It’s gone from producing if-else style code to producing a jump table. Now the code simply ensures that the input value is <= 42 and, if so, it uses that input value to calculate an offset into a table that’s 43 words long. It uses that offset to load the address of the code it should jump to. If the input value is larger than 42, it jumps to the default case. Again, with mild interest, note that now the compiler is generating code that is time-constant for all input values <= 42. Because we’ve left a large gap in our switch between case 4 and case 42, the compiler has filled the table between those cases with the address of the default block. I’ll bet that, if we made that gap large enough–perhaps jumping from 4 to 1000–the compiler would change its strategy again and produce more conditional logic to avoid creating a huge table full of the same, default case. But I’m leaving that as an exercise for you. In fact, I highly recommend playing around with the switch statement. It’s fun to see how many different strategies you can get the compiler to generate. Try adding and omitting break statements, nesting switches, etc. Now, let’s look at a different example. A table fetch:

static uint8_t table_fetch ( uint8_t inputValue ) { static uint8_t const table [ ] = { 0x01 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x01 , 0x00 , 0x00 , 0x01 , 0x01 , 0x01 , 0x01 , 0x00 , 0x00 } ; return table [ inputValue & 0x0F ] ; } static uint8_t table_fetch(uint8_t inputValue) { static uint8_t const table[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 }; return table[inputValue&0x0F]; }

For ARM, this disassembles to the following:

; static uint8_t table_fetch(uint8_t inputValue) { sub sp , sp , # 8 ; Prepare stack pointer mov r3 , r0 ; Transfer inputValue to r3 strb r3 , [ sp , # 7 ] ; Save inputValue on the stack for later use ; static uint8_t const table[] = { ; 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, ; 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 ; }; ; return table[inputValue&0x0F]; ldrb r3 , [ sp , # 7 ] ; load inputValue from the stack and r3 , r3 , # 15 ; inputValue &= 0x0F ldr r2 , . L6 ; load table address into r2 ldrb r3 , [ r2 , r3 ] ; load value from table ; } mov r0 , r3 ; Move the loaded table value into r0 add sp , sp , # 8 ; clean up stack bx lr ; return ; Pointer to table: . L6 : . word table . 5389 ; address of table ; Table: table . 5389 : . byte 1 , 1 , 0 , 0 , 0 , 0 , 1 , 1 . byte 0 , 0 , 1 , 1 , 1 , 1 , 0 , 0 ; static uint8_t table_fetch(uint8_t inputValue) { sub sp, sp, #8 ; Prepare stack pointer mov r3, r0 ; Transfer inputValue to r3 strb r3, [sp, #7] ; Save inputValue on the stack for later use ; static uint8_t const table[] = { ; 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, ; 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 ; }; ; return table[inputValue&0x0F]; ldrb r3, [sp, #7] ; load inputValue from the stack and r3, r3, #15 ; inputValue &= 0x0F ldr r2, .L6 ; load table address into r2 ldrb r3, [r2, r3] ; load value from table ; } mov r0, r3 ; Move the loaded table value into r0 add sp, sp, #8 ; clean up stack bx lr ; return ; Pointer to table: .L6: .word table.5389 ; address of table ; Table: table.5389: .byte 1, 1, 0, 0, 0, 0, 1, 1 .byte 0, 0, 1, 1, 1, 1, 0, 0

That’s relatively straightforward. But, now let’s switch architectures and take a look at what’s generated for the Microchip PIC16 family of processors for the same C function:

; "data table" table_fetch@table : retlw 1 retlw 1 retlw 0 retlw 0 retlw 0 retlw 0 retlw 1 retlw 1 retlw 0 retlw 0 retlw 1 retlw 1 retlw 1 retlw 1 retlw 0 retlw 0 ; static uint8_t table_fetch(uint8_t inputValue) { _table_fetch : ; static uint8_t const table[] = { ; 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, ; 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, ; }; ; return table[inputValue&0x1F]; andlw 15 ; inputValue &= 0x0F ; inputValue is in the w register on entry addlw low ( table_fetch@table ) ; add the table's address to inputValue movlp high table_fetch@table ; switch to the table's page callw ; call into the table ; it returns a value in w pagesel $ ; select current page return ; return to caller ; "data table" table_fetch@table: retlw 1 retlw 1 retlw 0 retlw 0 retlw 0 retlw 0 retlw 1 retlw 1 retlw 0 retlw 0 retlw 1 retlw 1 retlw 1 retlw 1 retlw 0 retlw 0 ; static uint8_t table_fetch(uint8_t inputValue) { _table_fetch: ; static uint8_t const table[] = { ; 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, ; 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, ; }; ; return table[inputValue&0x1F]; andlw 15 ; inputValue &= 0x0F ; inputValue is in the w register on entry addlw low (table_fetch@table) ; add the table's address to inputValue movlp high table_fetch@table ; switch to the table's page callw ; call into the table ; it returns a value in w pagesel $ ; select current page return ; return to caller

That “data table” at table_fetch@table is actually a bunch of “return with literal in w” instructions. What’s going on here? Well, the PIC16 family uses a Harvard architecture. This means, amongst other things, that its code and data are addressed differently. In the case of the PIC16 architecture, there is no way for an instruction to access code memory as data. To embed a table in code, the PIC16 compiler must produce a sequence of “return with literal” instructions and call into that sequence at the appropriate point based upon the table lookup input value. And you wind up with crazy code like the above. The point? Well, assembly patterns will vary from architecture to architecture and compiler to compiler. If that sounds daunting, know that once you can read one architecture’s assembly well, it’s relatively simple to pick up another’s because many of the patterns overlap. In fact, you can often pick up a new architecture from patterns and context alone–enough to get the gist of what a code snippet is doing anyways–without really learning the instruction set. Fun. BTW, I really can’t recommend wading through PIC16 assembly–I’d stick to ARM for educational purposes. It’s no wonder that Microchip refers to their newer, modified Harvard architecture processors as having a “C Compiler optimized architecture” (see the PIC18 series). But as painful as it is to develop for the old PICs, it’s amazing that there’s a working–and decent–C compiler for them. I must humbly bow to the compiler writers, for their work is Good. And I’ll conclude by thanking Github, for their work is great.

Misc notes: