1 | #define _assert( expression ) if ( !( expression ) ) { __debugbreak( ); }
|
1 2 3 4 5 6 | if ( a ) stb_assert( expression_a ); else if ( b ) stb_assert( expression_b ); else stb_assert( expression_c ); |
1 2 3 4 5 6 7 8 9 10 | if ( a ) if ( expression_a ) { __debugbreak( ); }; else if ( b ) if ( expression_b ) { __debugbreak( ); }; else if ( expression_c ) { __debugbreak( ); }; |
1 2 3 4 5 6 7 8 9 10 11 | #ifdef __cplusplus extern "C" { #endif _CRTIMP void __cdecl _wassert(_In_z_ const wchar_t * _Message, _In_z_ const wchar_t *_File, _In_ unsigned _Line); #ifdef __cplusplus } #endif #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) ) |
1 | #define _assert( expression ) ( ( expression ) || __debugbreak( ) )
|
1 2 3 4 5 6 7 8 9 10 11 | #define assert(x) ((void)((x) || (__assert_fail(#x, __FILE__, __LINE__, __func__),0))) #ifdef __cplusplus extern "C" { #endif _Noreturn void __assert_fail (const char *, const char *, int, const char *); #ifdef __cplusplus } #endif |
1 | #define _assert( expression ) do { if ( !( expression ) ) { __debugbreak( ); } while(0) |
1 | #define _assert( expression ) ( ( expression ) || __debugbreak( ) ) |
1 | #define _assert( expression ) ( ( expression ) || (__debugbreak( ), 0) ) |
mmozeiko
Not sure, but maybe that cast is to prevent compiler to generate warning about value not being used?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static inline long syscall(long num, long arg0, long arg1, long arg2, long arg3, long arg4, long arg5) { register long r10 __asm__("r10") = arg3; register long r8 __asm__("r8") = arg4; register long r9 __asm__("r9") = arg5; long ret; __asm__ __volatile__( "syscall" : "=a"(ret) : "a"(num), "D"(arg0), "S"(arg1), "d"(arg2), "r"(r10), "r"(r8), "r"(r9) : "memory", "rcx", "r11"); return ret; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void no_crt_main( void ) { /* No guarantee that the assembly will be the first instruction, so RSP might have change. */ uint64_t* rsp; __asm__ __volatile__ ( "movq %%rsp, %0" : "=r"( rsp ) ); int argc = ( int ) *rsp; rsp++; char** argv = (char** ) ( *rsp ); rsp += ( argc + 1 ); char** envp = (char** ) ( *rsp ); int status = main( argc, argv ); no_crt_exit( status ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | global _start extern main section .text _start: mov rdi, [rsp] ; argc lea rsi, [rsp + 8] ; argv lea rdx, [rsi + rdi * 8 + 8 ] ; envp mov rbp, rsp ; Set initial stack base pointer. call main mov rdi, rax ; Exit status value in rdi. mov rax, 231 ; exit_group syscall ; This is only for Linux, Windows uses different conventions. ; Stack grows downward from high address to low address. ; When the program starts: ; - rsp seems to be 16 bytes aligned (I didn't find documentation on that); ; - rsp points to the argument count value; ; - rsp + 8 points to argv; ; - argv + argc * 8 + 8 (argv is followed by a null pointer) points to envp. ; rsp points to the last thing that was pushed on the stack (if you write at rsp you override the last value that was pushed). ; When you use "push" it decrement rsp, then store the value at rsp. ; When you use "pop" it reads the value at rsp, then increment rsp. ; When calling a function, we put value in register or on the stack if we filled the available registers. ; So arguments are part of the caller stack frame. ; When putting arguments on the stack, if the value is less than 64 bits, we still push 64 bit of space but use only the required size. ; (that's what I observed, I didn't find documentation on that). ; That means that to access the value we still compute the address as a 64 bit value. ; Integer parameters can be passed in: rdi, rsi, rdx, rcx, r8, r9 ; Floating point parameters can be passed in: xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 ; Compound types can pass fields in registers. ; Compound types of more than 4 (2?) 64 bits bit value are passed on the stack (that's my understanding). ; The call instruction will decrement rsp by 8 and then set the return pointer (rip+ size of the call instruction) at rsp. ; Before a "call" instruction, rsp must be 16 bytes aligned. ; From x64 ABI: "The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on the stack) byte boundary." ; That means that when executing the first instruction of a function, rsp + 8 must be a multiple of 16 (since the return pointer was pushed on the stack). ; Upon entering a function, stack arguments will be at rsp + 8, the scond as rsp + 16... ; Does that mean that the alignment for the stack must be before pushing arguments ? ; rbp is generally used as the base stack pointer. So we want it to have the value of rsp upon enterring a function. ; But we want to restore rbp before exciting the function so we need save its value by pushing in on the stack upon enterring a function. ; So rbp is the value of rsp when you entered the funciton - 8. ; push rbp ; mov rbp, rsp ; Now the first stack argument is rsp + 16 or rbp + 16. ; We need to restore rbp before exiting the function. We can use mov (or pop if rsp is at the right place). ; We also need to set rsp to point at the return value before calling "ret". ; ret will increment rsp after reading the return pointer. ; rbp, rbx, r12, r13, r14, r15, belong to the caller, the called function need to save and restore those values. ; The abi doesn't list rsp as belonging to the caller, but it's needed to be restored for the program to work (my interpretation). ; If the return value needs to be put on the stack, the caller reserve space, and pass the address of that space in rdi. rax will contain a copy of rdi when the function returns. ; integer values can be returned in rax or rdx:rax (high:low). ; floats can be returned in xmm0 or xmm1:xmm0 (high:low). |
1 2 3 4 5 | #! /bin/bash
clang main.c -nostdlib -g -O0 -c -o main.o
nasm -felf64 no_crt_main.asm -o no_crt_main.o
ld main.o no_crt_main.o -nostdlib -o no_crt.bin
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | global _start extern main section .text _start: mov edi, [esp] ; argc lea esi, [esp + 4] ; argv lea edx, [esi + edi * 4 + 4] ; envp mov ebp, esp ; Set initial stack base pointer. ; Push argc, argv, envp right to left. push edx push esi push edi call main mov edi, eax ; exit status value in edi. mov eax, 252 ; exit_group int 0x80 ; syscall ; ebp, ebx, edi, esi, esp belong to the caller, the called function need to save and restore those value. ; The x86 abi actually specifies that arguments pushed on the stack that are less than a word (32 bits) must be tail padded to be a multiple o a word. ; The caller must reserve space in its stack frame for returning struct/union and pass the pointer as the first argument (the last pushed on the stack). ; The called must remove that address from the stack before returning. ; ABI example: ; pop eax => eax contains the return address, esp points to the struct return pointer ; xchg [esp], eax => copy the content of eax (return address) at esp, copy memory pointed by esp (struct return pointer) in eax ; push ebp => back to normal and the struct return pointer isn't on the stack anymore. ; ... ; mov eax, [ebp - 4] ; leave ; ret ; float and double are passed on the stack. double don't need to be 8 bytes aligned. |
1 2 3 4 5 | #! /bin/bash
clang main.c -nostdlib -g -O0 -c -o main.o
nasm -felf32 no_crt_main_x32.asm -o no_crt_main.o
ld main.o no_crt_main.o -nostdlib -o no_crt.bin
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | int main(int argc, char** argv, char** envp); __asm__( ".global _start \n" "_start: \n" " movq 0(%rsp), %rdi \n" " leaq 8(%rsp), %rsi \n" " leaq 8(%rsi,%rdi,8), %rdx \n" " movq %rsp, %rbp \n" // not sure why you do this. ABI doc seems to say that rbp should be set to 0 " call main \n" " movq %rax, %rdi \n" " movl $231, %eax \n" " syscall \n" ); |
mmozeiko
movq %rsp, %rbp // not sure why you do this. ABI doc seems to say that rbp should be set to 0.
; When putting arguments on the stack, if the value is less than 64 bits, we still push 64 bit of space but use only the required size.
; (that's what I observed, I didn't find documentation on that).
mmozeiko
; rbp is generally used as the base stack pointer. So we want it to have the value of rsp upon entering a function.
You can do that, or you can ignore this and use rbp as generic purpose register for your own needs. There's no requirement to use it as base stack pointer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | __asm__( ".intel_syntax noprefix\n" ".global _start \n" "_start: \n" " mov rdi, [rsp] \n" " lea rsi, [rsp + 8] \n" " lea rdx, [rsi + rdi * 8 + 8 ] \n" " xor rbp, rbp \n" " call main \n" " mov rdi, rax \n" " mov rax, 231 \n" " syscall \n" ".att_syntax prefix\n" // switches back to AT&T for rest of the code ); |