Last active
November 15, 2019 02:07
-
-
Save SeijiEmery/b1ec5ce32ee9040740dcbc8a0cae9c9a to your computer and use it in GitHub Desktop.
assembly notes, etc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ; | |
| ; nasm / masm comparison. | |
| ; | |
| ; First I'm going to briefly discuss how nasm does things, because it's | |
| ; simpler, and then show how this applies to masm. | |
| ; | |
| ; This should hopefully improve your understanding of masm, and | |
| ; understand what its quirks are and why + how it does things. | |
| ; | |
| ; Nasm has a very straightforward register syntax: | |
| ; eax => value in eax. | |
| ; [eax] => value pointed to by eax. | |
| ; | |
| ; as such, instructions that operate on two src / dst register | |
| ; operands _always_ operate as follows: | |
| ; | |
| ; mov eax, ebx => stores value in ebx into eax | |
| ; mov [eax], ebx => stores value in ebx into 32-bit value pointed to by eax. | |
| ; mov eax, [ebx] => stores 32-bit value pointed to by ebx into eax. | |
| ; mov [eax], [ebx] => invalid. x86 does NOT support memory-memory operands. | |
| ; | |
| ; Note that this syntax applies to _all_ operations in nasm, including | |
| ; all register types, direct memory access (ie. non-register variables and | |
| ; static global values), and immediates (constant values encoded directly | |
| ; in instructions, like `add eax, 4`). | |
| ; | |
| ; If we have a value in memory w/ a variable label of 'foo', | |
| ; | |
| ; mov eax, [foo] => loads 32-bit value at foo into eax | |
| ; mov ax, [foo] => loads 16-bit value at foo into ax (since ax is a 16-bit register) | |
| ; mov al, [foo] => loads 8-bit value at foo into al (since al is an 8-bit register) | |
| ; | |
| ; Nasm values are not typed, so you can freely reinterpret memory as 8-bit, | |
| ; 16-bit, etc., since that's how the underlying x86 architecture actually works: | |
| ; x86 instructions _are_ typed, but they're typed by the size of both operands, | |
| ; not specific registers or anything. AT&T syntax is maybe a bit closer to the | |
| ; actual opcodes, and it divides instructions like mov into movl, movw, movb, | |
| ; etc. | |
| ; | |
| ; Value sizes are also always inferred from the register sizes, with one | |
| ; exception for immediate values + memory, ie. where you're moving a constant | |
| ; value into some location in memory, so you need to explicitely state what | |
| ; the memory value should be interpreted as: | |
| ; | |
| ; mov [eax], BYTE 4 => stores 4 into the value pointed to by eax as a single byte. | |
| ; mov [foo], BYTE 4 => stores 4 into the memory address foo, treating foo as a single byte. | |
| ; mov [eax], WORD 4 => 16-bit value (2 bytes) | |
| ; mov [foo], WORD 4 => ... | |
| ; mov [eax], DWORD 4 => 32-bit value (4 bytes) | |
| ; mov [foo], DWORD 4 => ... | |
| ; | |
| ; Now, how does this relate to MASM? | |
| ; | |
| ; MASM has more rules, but is very easy to understand once you know how the | |
| ; concepts above work, and how microsoft decided to implement them in their | |
| ; assembly language. | |
| ; | |
| ; In masm, register-register operands work the same: | |
| ; mov eax, ebx => move value at eax to ebx. | |
| ; | |
| ; But, they decided to add variable read/write w/out brackets as a convenience | |
| ; for x86 DOS programmers: | |
| mov eax, [foo] ; nasm: mov eax, [foo] | |
| mov eax, foo ; nasm: mov eax, [foo] | |
| mov foo, eax ; nasm: mov [foo], eax | |
| mov [foo], eax ; nasm: mov [foo], eax | |
| ; This means that they needed a different syntax for loading the _address_ of a | |
| ; variable, which in NASM is just `mov eax, foo`: | |
| mov eax, OFFSET foo ; load address of foo | |
| ; And nasm has a type system, which means that if 'foo' is declared as a 32-bit | |
| ; variable, 'mov ax, foo' is invalid (good), but that means it needs additional | |
| ; syntax for when you want to actually _reinterpret_ this value. Hence: | |
| mov ax, WORD PTR [foo] ; in nasm: mov ax, [foo] | |
| mov BYTE PTR [foo], al ; in nasm: mov [foo], al | |
| mov eax, [foo] ; type matches, so no extra syntax | |
| mov eax, DWORD PTR [foo] ; though this also works... | |
| ; And if you're reading from a register that you're using as a pointer, well, | |
| ; MASM's type system just kind of gives up at that point, so you'll always need | |
| ; to use the <T> PTR syntax: | |
| mov eax, DWORD PTR [ebx] ; in nasm: mov eax, [ebx] | |
| mov WORD PTR [ebx], ax ; in nasm: mov [ebx], ax | |
| mov al, BYTE PTR [ebx] ; in nasm: mov al, [ebx] | |
| ; Of course, the type system _does_ have some benefits: there is no TYPE, | |
| ; LENGTHOF, or SIZEOF directives in nasm, so if you're reading / writing | |
| ; to / from global variables a lot, MASM is nicer, and you'll have to roll | |
| ; your own solution to do the same in nasm (ie. hardcode the TYPE values, | |
| ; get the equivalent of SIZEOF via $ - <my_label>, and LENGTHOF from there, | |
| ; etc). | |
| ; | |
| ; On the flip side, if most of your code is generalized routines that operate | |
| ; on registers (eg. a string algorithm where your inputs are esi / edi | |
| ; pointers, and ecx for the string length), then NASM will be nicer. | |
| ; | |
| ; | |
| ; In both "languages" (NASM vs MASM syntax), the way you work with arrays is | |
| ; similar. Either you a) treat the array as a pointer, or b) use, eg., a global | |
| ; variable + index pointer, w/ index access scaled by a constant power of 2 | |
| ; (x86 has this as a hardware feature iirc). | |
| ; | |
| ; Using MASM syntax, suppose we have an array like this, and want to write some | |
| ; code to sum the array into eax. | |
| .data | |
| myArray DWORD 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 | |
| .code | |
| ; Using indices: | |
| sumArrayUsingIndex PROC USES esi ecx | |
| ; Here we'll basically want to emulate this C for loop: | |
| ; | |
| ; int acc = 0; | |
| ; for (int i = 0; i < myArray_length; ++i) | |
| ; acc += myArray[i]; | |
| ; | |
| ; Or this, which maps directly to our asm code: | |
| ; int acc = 0; | |
| ; for (int i = 0, count = myArray_length; count --> 0; ++i) | |
| ; acc += myArray[i]; | |
| ; | |
| mov esi, 0 ; esi used as our index 'i'. | |
| mov ecx, LENGTHOF myArray ; and this is the # times we want to iterate | |
| xor eax, eax ; clear eax (note: this is equivalent to mov eax, 0, | |
| ; but preferred b/c modern x86 processors specifically | |
| ; optimize for it). | |
| L1: | |
| ; Note: myArray[ esi * TYPE myArray ] and [ myArray + esi * TYPE myArray ] | |
| ; are equivalent (former is translated into the latter). | |
| ; This uses an x86 feature that lets you do | |
| ; [ <reg> * <const power-of-two scalar> + <const offset> ] | |
| ; in one instruction (read / write). | |
| add eax, myArray[ esi * TYPE myArray ] ; read array value + add | |
| inc esi ; move index forward by 1 each iteration | |
| loop L1 ; iterate until ecx == 0. | |
| ret | |
| sumArrayUsingIndex ENDP | |
| ; Using pointers: | |
| sumArrayUsingPointer PROC USES esi ecx | |
| ; Here we want to do this loop: | |
| ; | |
| ; int acc = 0; | |
| ; for (int* ptr = &myArray[0]; ptr < &myArray[myArray_length]; ++ptr) | |
| ; acc += *ptr; | |
| ; | |
| ; Which we'll translate into the following (once again, maps directly to asm) | |
| ; int acc = 0; | |
| ; for (int *ptr = myArray, count = myArray_length; count --> 0; ++ptr) | |
| ; acc += *ptr; | |
| ; | |
| mov esi, OFFSET myArray ; esi used as a pointer | |
| mov ecx, LENGTHOF myArray ; store our iteration count | |
| xor eax, eax | |
| L1: | |
| add eax, DWORD PTR [esi] ; load value directly at esi | |
| add esi, TYPE myArray ; increment pointer by 4 bytes each iteration | |
| ; (note that we were multiplying by TYPE before) | |
| loop L1 ; iterate until ecx == 0. | |
| ret | |
| sumArrayUsingIndex ENDP | |
| ; For kicks, here's the nasm version: | |
| section .data ; equiv to .data | |
| ; dd: double-word data; you _can_ use this syntax in MASM, or the DWORD | |
| ; syntax in NASM. | |
| myArray: dd 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 | |
| .length: dd ($ - myArray) / 4 ; we _explicitely_ need to calculate + store the array length | |
| section .text ; equiv to .code | |
| sumArrayUsingIndex: ; procs are just labels | |
| push esi ; don't have 'USES', so we'll preserve registers manually | |
| push ecx | |
| mov esi, 0 | |
| mov ecx, myArray.length | |
| xor eax, eax | |
| ; In MASM, labels are local inside of a PROC ... ENDP block. | |
| ; In NASM, local labels are created w/ .<label> (and can be accessed using | |
| ; <parentLabel>.<childLabel>, eg. myArray.length, or sumArrayUsingIndex.L1) | |
| .L1: | |
| add eax, myArray[ esi * 4 ] ; or add eax, [ myArray + esi * 4 ] | |
| inc esi | |
| loop .L1 | |
| pop ecx ; restore the registers we used, excluding eax which we'll return the sum in | |
| pop esi | |
| ret | |
| sumArrayUsingPointer: | |
| push esi | |
| push ecx | |
| mov esi, myArray | |
| mov ecx, myArray.length | |
| xor eax, eax | |
| .L1: | |
| add eax, [esi] | |
| add esi, 4 | |
| loop .L1 | |
| pop ecx | |
| pop esi | |
| ret | |
| ; | |
| ; Note: NASM is actually a more recent assembly language, and was created as | |
| ; a reaction against MASM and GAS (the latter of which uses horrible AT&T | |
| ; syntax). Its syntax is arguably more sensible and straightforward b/c its | |
| ; creators had the benefit of hindsight, though it's also very minimalistic | |
| ; and unambitious, and lacks a number of MASM features. | |
| ; | |
| ; For more specifics on syntactic differences, see | |
| ; https://courses.engr.illinois.edu/ece390/archive/mp/f99/mp5/masm_nasm.html | |
| ; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Visual studio fixes: | |
| – disable edit and continue: | |
| https://msdn.microsoft.com/en-us/library/7yty6a48.aspx | |
| This is super annoying and not even remotely useful for editing x86 assembly. | |
| – when a popup shows up saying there was a build failure, stop or use the old exe, | |
| select no (or whatever will make the build fail noisily + show error messages), | |
| and check always apply to get that annoying message out of your face. | |
| – yay, no more popups each time you rebuild! | |
| Irvine32 documentation: | |
| – The irvine32 implementation is in C:\Irvine\Examples\Lib32 | |
| – Two files are relevant: irvine32.inc, and irvine32.asm | |
| – The .inc file has color definitions and lists the functions available (but doesn't tell you what the parameters and side effects are!) | |
| – The .asm file has a detailed listing of function parameters and effects (dunno why this isn't in the .inc), and the full implementation so you can double check, etc. Just open that in visual studio or your favorite text editor + search "<fcn_name> PROC" to jump to the relevant documentation. | |
| – There's also a Macros.inc file which you could include into your projects / assignments. Basically, it's a bunch of helper utilities that should make writing irvine code easier + more concise, though you should read the irvine chapter on macros first. | |
| Other suggestions: | |
| This is fairly general, but I'd recommend getting sublimetext, and the github student developer pack: | |
| https://www.sublimetext.com/ | |
| https://education.github.com/pack | |
| Both are industry standard fyi (espescially in, say, web development in sf). | |
| Note: two alternatives to sublime are visual studio code and atom: | |
| https://code.visualstudio.com/ | |
| https://atom.io/ | |
| All 3 of these are very good and monumentally better than say, notepad++ or textwrangler. | |
| If you're writing c++ / asm code in notepad.exe, I will strangle you. | |
| If you want to use sublimetext to edit (but not run) x86 assembly code, install | |
| the package manager and there's at least 2 x86 packages that give you _much_ | |
| better syntax highlighting than visual studio (and you get partial autocomplete, | |
| block commenting and indent/dedent, etc; the VS text editor is pretty terrible | |
| and missing a bunch of features if you're not editing C++ / C# / xml or whatever). | |
| You could run sublime (probably at home) alongside visual studio, or use makefiles + | |
| commandline if you want to do assembly homework mostly in sublime, for example. | |
| Opening a directory in sublime gives you a project sidebar w/ the file hierarchy. | |
| Sublime also has an extensible build system system, so you could turn it into | |
| an x86 or c++ IDE if you really wanted to, though you'd be missing a visual | |
| debugger + breakpoints (that's fine though, just build everything using TDD | |
| and you don't need a debugger :P). | |
| It's also _really_ convenient to setup a private repo to do all your homework in. | |
| Visual studio has git support builtin, so you can just login, clone your repo, | |
| make changes, and then push when you leave to edit at school and home / whatever. | |
| Or you could just keep using a flash drive I guess; that works pretty well on | |
| windows... |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: wrote this for some members of my x86 Assembly + Computer Organization class, as some students were struggling since MASM syntax is very weird and inconsistent. MASM + Visual Studio 2015 also sucks balls* and personally, I vastly prefer NASM + Makefiles + Sublime Text (though I had to learn it myself, and ended up writing my own libraries – see https://github.com/SeijiEmery/comp160).
*Crappy pathetic excuse for a text editor, missing tons of keyboard shortcuts if you're not editing C#/C++/XML, and no syntax highlighting or autocomplete, terrible project management + UI with too many things buried in hard to find locations, and the worst godawful toolchain I've ever had the displeasure of using (MASM's parser splits out completely useless and unhelpful error messages if you violate its sometimes very weird + bizarre syntax – which indicates a badly written recursive descent parser not updated since the early 90's, though it does have a public BNF spec so yay – and it sometimes takes ~5 seconds (5000 ms!) to assemble a 1kb text file. PLUS, visual studio itself can take 40s to login + load your f***ing settings – which is really just creating a user profile directory + copying (or oh god, probably generating in the worst way possible) some xml files. I should note that I was using crappy school computers for windows builds (not development, thank god), and while crappy, they apparently had i7's and 8gb of ram, so anything short of instantaneous for an assembler is simply unacceptable, and microsoft clearly, clearly still has massive quality + optimization issues, since this shittyness extends well into their c++ tools too).