Single step through functions in a debugger to see how things work, and for example how a stack frame is set up. Pay attention how registers and flags are affected by the execution. See how conditional branches are affected by the flags.
When you're looking at the code, remember that there are often weird looking details, like unused portions in stack and in compiled function codegen (loops, entry points) for alignment purposes — modern CPUs hate unaligned things.
Note: Some x86 instructions have implicit register use that might not be directly obvious. Like PUSH, POP, IMUL, IDIV, LOOP, STOS[BWD], MOVS[BWD], etc. They can affect registers that are not mentioned in the instruction operands.
In general, if the things look weird, just google the instruction. Much less surprises in other mainstream architectures, like ARM. All architectures do have vector instructions that might confuse you at first, like x86 vectored double precision add, VADDPD. Again, just web search them. No one remembers all of the instructions by heart, there's just no point.
Web search for assembler tutorials and simulators. They're too many to list, just pick something suitable for your taste.
In short, play around. Don't get scared by something weird, just look it up.
Don't stress if you don't understand everything, you can always look it up or try it out in a debugger. Even a little bit can help quite a bit.
JITs usually have some way to display generated assembler. For example, to see the native x86/ARM/whatever code generated by JVM you'd say something like:
> Again, just web search them. No one remembers all of the instructions by heart, there's just no point.
Another good tip: download the official architecture manuals, they're freely available for the most relevant major architectures (and for x86, both Intel and AMD have their own version). They're monstrous doorstoppers (several volumes with many thousand pages), but include in excruciating detail the description of every single instruction, with the advantage that they'll work even if your Internet connection is offline.
> but include in excruciating detail the description of every single instruction
well, at least the official ones with the officially supported operands. Sandsifter has a lot to say about how many undocumented instructions exist on just the "exposed" x86 part of the CPU, nevermind the ARM and other sub ring-0 stuff.
Yes, but in the context of this thread: undocumented instructions can change from generation to generation of a processor architecture, while documented instructions are much more stable; and because of that, compilers will not generate undocumented instructions. When reading compiler output, the official documentation of the instructions (plus documentation of the calling conventions) should be enough.
I'm still a bit of an assembly/JIT newbie, but a few thoughts:
When you write assembly code, it's generally specific to several details, which in turn affects your choice of tutorials from which to work:
1) The target architecture (x86-64, AArch64, MIPS, etc.) This defines what instructions you have available, the memory model, etc.
2) The architecture ABI. E.g., what's the protocol for passing parameters to functions you call, and for receiving the return code. [0]
3) The particular assembler you plan to use. Gnu As (and I assume others) provide some directives that don't map directly to machine instructions, but do some book-keeping to make your life easier.
4) (Potentially) the file format for the resulting code [1]. I think this is one area where the assembler and linker utilities can shelter you from the ugly details.
FWIW regarding JIT: Lately I've started playing around with Xbyak [2]. It's probably a bit light on the documentation, but for the most part I've found it to be an easy way to get started with JIT.
I look at a fair amount of disassembly. One tip to understand something complex in the debugger is to skip to callsites if they are available in the interesting part of the code. The compiler usually has to put values in conventional locations, either as arguments or live-through variables, so your variables are easier to track.
Make it more accessible from your build system. That way, when your curiosity is piqued (or frustration has peaked...), its easy to pick it up and see for yourself.
With GCC, its as simple as adding `-save-temps=obj`. You get preprocessed source and assembly emitted alongside the object files.