The faker's guide to reading x86 assembly language
timdbg.com
timdbg.com
One can learn the vague syntax complexities, etc. - maybe even write a Hello World..but without having real-world exposure of what compilers actually do to source code, people cannot actually derive meaningful signal or intuition. A very experienced programmer without exposure to assembly might be able to optimize a tight loop or spot a performance issue, but that's probably the extent of it.
Complexity increases as you introduce platform-specific calling conventions, APIs, etc.
I don't know a better way to acquire assembly intuition than by implementing a RISC-V CPU or staring at a lot of decompiled output.
It on my todo list at some point, maybe this year or next.
I imagine an Assembly reader sees these things. I wonder if there’s a guide like this for them.
Other than that, I like "Assembly Language Step By Step" by Jeff Duntemann, which is currently in its third edition and is Linux-only, as opposed to previous editions which were MS-DOS and Linux. He has example assembly code (Intel syntax for NASM, another reason I like his book) for download on his website:
http://www.duntemann.com/assembly.html
My only annoyance is that apparently, even the most recent edition from 2009 is still 32-bit only.
Anyway, beyond that, you'd be looking for information on using assembly language to perform some specific task, like writing vectorized numerical code.
https://wiki.osdev.org/Calling_Conventions
Richard Blum [2] after Dunteman.
[1] At a reasonable level of approximation.
[2] https://www.wiley.com/en-us/Professional+Assembly+Language-p...
"32-bit x86 code" just means "code that does not use any of the AMD64 ISA." (And at this point, probably SSE2+ and a handful of other extensions.)
GP is correct: anyone that "knows" AMD64 assembly inherently must know 32-bit ASM because one must use the original x86 registers, instructions, etc. for the vast majority of tasks.
People are still constantly running what was formerly known as "32-bit x86 code" in their 64-bit applications.
1. Data movements (mov)
2. Control flow (various jumps with corresponding tests, call and ret)
3. Calling convention. But you can often wing this and figure it out from context.
Like the Good Lord intended.
Is this really still the case with modern caching branch-predicting microcode processors? [1]
From what I know (which is little), there is quite a way between assembly and what a processor will actually execute.
[1] just throwing around buzzwords
Perhaps it's more correct to say assembler is 'closer to the truth'
So assembly is evidently not always the truth. It is however the closest we can easily get to the truth (on consumer desktop/server grade processors).
Higher level languages will have numerous control structures to iterate, consider everything from do-until, do-while, for loops, all the way up to functional languages. That's great. It all gets translated down to machine language code eventually at some low level, either at compile or run time.
Most people iterate in assembly the "simplest" way which will vary by architecture but is generally not overly abstract.
Similar to how its possible to write in an OO style (inheritance, polymorphism, etc) in non-OO languages but most people writing in non-OO languages do not. I think it would be possible to write a bash shell script that implements the concept of polymorphism, but most people would never do that. Its possible and funny to write thousands of lines of code to do "enterprise grade patterns hello world" but most shell scripters will "echo hello world" and call it good which makes it more truthy, less abstract, less obscured.
True, it'll be impossible to infinitely extend and scale the bash "hello world" script, but most software problems are never infinitely extended and scaled anyway. The most powerful tool for a job is rarely the correct tool for the job.
Well, modern processors can execute out of order for efficiency reasons, but the end result must be exactly the same as if the instructions were executed serially.
Other than that, this piece looks good as far as it goes.
Furthermore, the ability to put a memory operand on, say, an ADD instruction is close in effect to having a compressed instruction encoding that encodes "LD to a temporary, unnamed register followed by ADD that register to the destination register" in fewer bytes than having both (also avoids clobbering a register, useful given the thin 8 registers 32-bit x86 has).
[1] Okay, several, to vary the operand size (8-bit, 16-bit, 32-bit, 64-bit).
You could say the same thing about the VAX, which was even more CISC than the x86 is, because of how its opcode encoding worked: Bytes for the opcode, bytes for addressing modes, bytes for register specifications or constant values or memory addresses. Ditto the PDP-10. Which is to say that's not a helpful way of making the RISC/CISC divide because it ignores everything that makes those kinds of processors different.
Somewhat down in this page, written by processor designer John Mashey, is a list of features which RISC processors tend to have that CISC processors don't:
https://userpages.umbc.edu/~vijay/mashey.on.risc.html
Above that list are two points which get to the heart of the RISC project:
> The RISC characteristics:
> a) Are aimed at more performance from current compiler technology (i.e., enough registers).
> OR
> b) Are aimed at fast pipelining
> - in a virtual-memory environment
> - with the ability to still survive exceptions
> - without inextricably increasing the number of gate delays (notice that I say gate delays, NOT just how many gates).
The point b is where RISC chips really pulled away from CISC in terms of architectural design, especially chips like the MIPS, which Mashey worked on: The MIPS had a number of points where it exposed the tricks it used to pipeline more aggressively, even at the expense of making compilers somewhat harder to write and/or human assembly-language programmers think a bit harder. However, the lack of complicated addressing modes (post-increment, scale-and-offset, etc.) and the lack of register-memory opcodes with ALU operations, and total lack of memory-memory operations, is still a very common feature of RISC design.
I also want to take on this:
> Furthermore, the ability to put a memory operand on, say, an ADD instruction is close in effect to having a compressed instruction encoding that encodes "LD to a temporary, unnamed register followed by ADD that register to the destination register" in fewer bytes than having both (also avoids clobbering a register, useful given the thin 8 registers 32-bit x86 has).
The difference between having one opcode which does both memory operations and ALU operations and not having those kinds of opcodes is faulting: If the CPU has to take a fault, does it have to back out a lot of ALU state such that opcodes appear to be atomic? CISC chips do, and they pay for it, whereas RISC chips are designed not to have to. This, again, makes pipelining easier. (And the page I linked to goes into this as well.)
CISC/RISC is points on a scale, but that doesn't mean it's helpful to "reinterpret" things to try and make CISC seem equivalent to RISC.
That was why I decided to drop the study temporarily. I wanted to figure out exactly the OS and the type of applications (e.g. DOS virus, or Windows malware) I'd like to reverse engineer for, gain some positive engineering experience and then come back.
https://news.ycombinator.com/item?id=33586476
which cites this PDF:
https://go.googlesource.com/go/+/refs/tags/go1.19.4/src/runt...
https://www.intel.com/content/www/us/en/developer/articles/t...
coding in assembler is very manual, such as stack manipulation, or dropping assembler instructions into windows applications.
these are procedures you can pickout from the code, but very hard to discover if you are unaware of the hueristic.
other resources.
xxii
-----
vii
hmmm... now what do i do? how about uppercase/lowercase? XXII
----- = III i iv ii viii ...
VIIInstead of upper/lower (romans didnt have lowercase?) I would define a latin word, say "dividum" and write it as
CCCXIV DIVIDUM C
Which for short would be:
CCCXIVDVDC
Where DVD is a nonsensical or at least redundant roman numeral (500 + 495 = 995 VM). For readability we now add a new symbol to unicode of a V and a D superimposed.
N.B. Latin nerds... I dont care what the proper term for "divided by" is.