4 ms·
It's hard to find a tutorial that really tells you what is going on. You have to start with whatever operating system you're on, then figure out what the heck y
by nsxwolf 8y ago
It's hard to find a tutorial that really tells you what is going on. You have to start with whatever operating system you're on, then figure out what the heck you're reading. What is x86 and what is some reserved word peculiar to the assembler you're using?
Here's macOS Hello, world code I see floating around a lot, and I can't make heads or tails of it:
global start
section .text
start:
push dword msg.len
push dword msg
push dword 1
mov eax, 4
sub esp, 4
int 0x80
add esp, 16
push dword 0
mov eax, 1
sub esp, 12
int 0x80
section .data
msg: db "Hello, world!", 10
.len: equ $ - msg
- pjc50 8y agoBasically everything left-aligned is for the assembler, and the indented material is the mnemonics for the x86 set. The assembler breaks down as: push dword msg.len push dword msg push dword 1 Place a pointer to the message, its length, and 1 on the stack. Each of these decrements esp by 4. mov eax, 4 We'll be doing syscall 4 in a sec sub esp, 4 Reserve 4 bytes for the return int 0x80 Do the syscall! See https://opensource.apple.com/source/xnu/xnu-2782.20.48/bsd/kern/syscalls.master https://opensource.apple.com/source/xnu/xnu-2782.20.48/bsd/k... ; this is 'write' add esp, 16 Discard the return value and arguments from the stack. push dword 0 mov eax, 1 sub esp, 12 int 0x80 Similarly setup and do syscall 1 to exit.
- nsxwolf 8y agoBut then there's that "msg.len", which I assume is some sort of assembler shortcut for getting the length of an array and not part of the x86.
- pjc50 8y agoThat's referring to the label msg.len, which is a value computed at compile time in the 'data' section at the bottom. msg: db "Hello, world!", 10 .len: equ $ - msg 'msg' is a label, which will be assigned to an address at compile time. 'db' short for 'declare bytes' puts some bytes at that address. '.len' is then defined as the current output address ($) minus the start of the string.
- e12e 8y agoIn addition to all these excellent comments, it can also be instructive to go "backwards" from C, here on Linux: cat hello.c #include <unistd.h> char str[] = "Hello, World!\n"; int main() { write(1, str, sizeof(str)-1); return 0; } cat hello.s .file "hello.c" .intel_syntax noprefix .globl str .data .align 8 .type str, @object .size str, 15 str: .string "Hello, World!\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc push rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 mov rbp, rsp .cfi_def_cfa_register 6 mov edx, 14 lea rsi, str[rip] mov edi, 1 call write@PLT mov eax, 0 pop rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0" .section .note.GNU-stack,"",@progbits Note the line: lea rsi, str[rip] which I belive is something along the lines of load effective address (lea), into rsi register, of "str" symbol offset with relative instruction pointer (rip - to allow for relative addressing). You can compile and run with: gcc -std=c11 hello.c && ./a.out Prouduce hello.s from hello.c with: gcc -std=c11 hello.c -S -masm=intel
- eterps 8y agoThis makes it even easier: https://news.ycombinator.com/edit?id=17341952 https://news.ycombinator.com/edit?id=17341952
- cesarb 8y ago"lea" is a fun instruction. Going piece by piece: on x86, each register has several sizes. For instance, rax is a 64-bit register, with its lower 32-bit half being eax, its lower 16-bit half being ax, and its lower 8-bit half being al (for historical reasons). So when a register name starts with "r", it's the 64-bit variant. Therefore, "rip" is the 64-bit address of the next instruction. The instruction "mov rsi, str[rip]" would get the 64-bit address of the next instruction, add to it a fixed offset (which the assembler and linker compute for you, as the exact offset you need to get to the data at the "str" label), load 64 bits from that address, and put the result in the "rsi" register. And that's not even the most complex addressing mode; you can get a register, add to it another register multiplied by 2, 4, or 8, add to it a constant, and use it as the memory address to load, store, or even modify in-place. The "lea" instruction (load effective address) is a way to get directly at the power of that complex address calculation logic for your own uses. Instead of using the computed memory address to get at the memory, it's the memory address that's put in the register. Therefore, where "mov rsi, str[rip]" would read from memory, "lea rsi, str[rip]" would put in rsi the memory address the "mov" would have read from. This also allows for a few tricks. For instance, you can use "lea" to multiply a number by five, without using the multiplier: just use a register and the same register scaled by 4 (and you can also add a constant to the result, still in a single complex instruction).
- kccqzy 8y agoSome tips for learning: 1. It's much easier to study the disassembly of object files than executables. Or better yet, use -S to ask the compiler to emit assembler. 2. Don't use hello world as your first program. Those involve either library calls or system calls. Strings too, and strings are not so straightforward in any systems programming language. Start with single functions (not called main) that take two arguments and add them/subtract them/multiply them. 3. Try learning x86-64 first. It's easier to learn because you don't have to know about the stack to understand and write simple arithmetic functions, if they take no more than six arguments. 4. It usually helps to pass -Os to the compiler to have it generate more compact code. You won't see lots of superfluous code to load/store things to the stack (for debugability) but you also won't see autovectorized code.