How to read from stdin and write to stdout in inline assembly gcc, just like we do it in NASM:
_start:
mov ecx, buffer ;buffer is a data word initialised 0h in section .data
mov edx, 03
mov eax, 03 ;read
mov ebx, 00 ;stdin
int 0x80
;Output the number entered
mov eax, 04 ;write
mov ebx, 01 ;stdout
int 0x80
I tried reading from stdin in inline assembly and then assign the input to x:
#include<stdio.h>
int x;
int main()
{
asm(" movl $5, %%edx \n\t" "
movl $0, %%ebx \n\t" "
movl $3, %%eax \n\t" "
int $0x80 \n\t "
mov %%ecx,x"
::: "%eax", "%ebx", "%ecx", "%edx");
printf("%d",x);
return 0;
}
However it fails to do so.
syscall from within GCC inline assembly
This link contains a code that is able to print only a single character to the stdout.
This code is based solely on my reading of linux references. I'm not on linux, so I cannot test it, but it should be pretty close. I would test it using redirection: a.out < foo.txt
#include <stdio.h>
#define SYS_READ 3
int main()
{
char buff[10]; /* Declare a buff to hold the returned string. */
ssize_t charsread; /* The number of characters returned. */
/* Use constraints to move buffer size into edx, stdin handle number
into ebx, address of buff into ecx. Also, "0" means this value
goes into the same place as parameter 0 (charsread). So eax will
hold SYS_READ (3) on input, and charsread on output. Lastly, you
MUST use the "memory" clobber since you are changing the contents
of buff without any of the constraints saying that you are.
This is a much better approach than doing the "mov" statements
inside the asm. For one thing, since gcc will be moving the
values into the registers, it can RE-USE them if you make a
second call to read more chars. */
asm volatile("int $0x80" /* Call the syscall interrupt. */
: "=a" (charsread)
: "0" (SYS_READ), "b" (STDIN_FILENO), "c" (buff), "d" (sizeof(buff))
: "memory", "cc");
printf("%d: %s", (int)charsread, buff);
return 0;
}
Responding to Aanchal Dalmia's comments below:
1) As Timothy says below, even if you aren't using the return value, you must let gcc know that the ax register is being modified. In other words, it isn't safe to remove the "=a" (charsread), even if it appears to work.
2) I was really confused by your observation that this code wouldn't work unless buff was global. Now that I have a linux install to play with, I was able to reproduce the error and I suspect I know the problem. I'll bet you are using the int 0x80 on an x64 system. That's not how you are supposed to call the kernel in 64bit.
Here is some alternate code that shows how to do this call in x64. Note that the function number and the registers have changed from the example above (see http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64):
#include <stdio.h>
#define SYS_READ 0
#define STDIN_FILENO 0
int main()
{
char buff[10]; /* Declare a buff to hold the returned string. */
ssize_t charsread; /* The number of characters returned. */
/* Use constraints to move buffer size into rdx, stdin handle number
into rdi, address of buff into rsi. Also, "0" means this value
goes into the same place as parameter 0 (charsread). So eax will
hold SYS_READ on input, and charsread on output. Lastly, I
use the "memory" clobber since I am changing the contents
of buff without any of the constraints saying that I am.
This is a much better approach than doing the "mov" statements
inside the asm. For one thing, since gcc will be moving the
values into the registers, it can RE-USE them if you make a
second call to read more chars. */
asm volatile("syscall" /* Make the syscall. */
: "=a" (charsread)
: "0" (SYS_READ), "D" (STDIN_FILENO), "S" (buff), "d" (sizeof(buff))
: "rcx", "r11", "memory", "cc");
printf("%d: %s", (int)charsread, buff);
return 0;
}
It's going to take a better linux expert than me to explain why the int 0x80 on x64 wouldn't work with stack variables. But using syscall does work, and syscall is faster on x64 than int.
Edit: It has been pointed out to me that the kernel clobbers rcx and r11 during syscalls. Failing to account for this can cause all sorts of problems, so I have added them to the clobber list.
Related
I'm working on a shared mem assignment for uni and I've 'borrowed' and 'massaged' some shellcode I've seen in posts I've read here and other places. I've been able to construct an example that runs on my MacBook (Mojave) and almost does what I want.
However, since I'm not that experienced with assembly programming in an OS environment (MacOS in this case), and because I don't fully understand the assembly below, I need a little help to overcome my last issue.
In my C boilerplate wrapper, I have a loop that calls my shellcode periodically, but the loop only executes one iteration. This leads me to the conclusion that the second syscall (in the code below) is performing an exit 0, thus terminating the process.
How can I modify the assembly to return instead of exit?
Note if asked, I can post more information about the wrapper code and tools I'm using.
bits 64
Section .text
global start
start:
jmp short MESSAGE ;00000000 EB24 jmp short 0x26
GOBACK:
mov eax, 0x2000004 ;00000002 B804000002 mov eax,0x2000004 ; write
mov edi, 0x1 ;00000007 BF01000000 mov edi,0x1
lea rsi, [rel msg] ;0000000C 488D3518000000 lea rsi,[rel 0x2b]
mov edx, 0xf ;00000013 BA0F000000 mov edx,0xf
syscall ;00000018 0F05 syscall
mov eax,0x2000001 ;0000001A B801000002 mov eax,0x2000001 ;exit
mov edi,0x0 ;0000001F BF00000000 mov edi,0x0
syscall ;00000024 0F05 syscall
MESSAGE:
call GOBACK
msg: db "Hello, world!", 0dh, 0ah
.len: equ $ - msg
Here is my C boilerplate code, which includes the shellcode from above.
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
const char shellcode[] = "\xeb\x24\xb8\x04\x00\x00\x02\xbf\x01\x00\x00\x00\x48\x8d\x35\x18\x00\x00\x00\xba\x0f\x00\x00\x00\x0f\x05\xb8\x01\x00\x00\x02\xbf\x00\x00\x00\x00\x0f\x05\xe8\xd7\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0d\x0a";
int main(int argc, char **argv)
{
void *mem = mmap(0, sizeof(shellcode), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
memcpy(mem, shellcode, sizeof(shellcode));
mprotect(mem, sizeof(shellcode), PROT_READ|PROT_WRITE|PROT_EXEC);
for (int i = 0; i < 10; i++) {
int (*func)();
func = (int (*)())mem;
(int)(*func)();
sleep(5);
}
munmap(mem, sizeof(shellcode));
return 0;
}
I have a loop that calls my shellcode
So if it's an actual call then you are good to go and the only change you need to do is to remove the 3 last instructions (mov, mov and syscall) and replaced them with a ret since the return address will be on the stack already. But we need to additionally clean up the stack a little bit since the address of the text string is put there for the sake of syscall, we want to get rid of it before the return.
Also since you shellcode contains an offset (the first jmp opcode with the value of 0x24) it's safer to not delete anything (as it will change the offsets) but to replace with NOP. Unless you modify the shellcode's source code and generate it every time - than it's fine to remove stuff.
So what I would actually propose to do is to replace the bytes that constitutes for the last 3 opcodes with 0x90(NOP) and the last 2 bytes from that replace with pop rax\ret so bytes 0x58 and 0xc3. The final shellcode could look like this:
const char shellcode[] = "\xeb\x24\xb8\x04\x00\x00\x02\xbf\x01\x00\x00\x00\x48\x8d\x35\x18\x00\x00\x00\xba\x0f\x00\x00\x00\x0f\x05\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x58\xc3\xe8\xd7\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0d\x0a";
I use the fgets() function in assembly and it should work, but I have a problem with my buffer. Is there a way to define a char pointer? I ask because the function needs a char pointer as the first argument.
Here you can see my code:
; nasm fgets.asm -f elf64 -o fgets.o
; gcc -no-pie fgets.o
; ./a.out
; Define fgets as an external function
extern fgets
SECTION .DATA
buffer: db "0000000000", 0
SECTION .TEXT
global main
main:
push rbp ; Push stack
; Set up parameters and call the C function
mov rdi, buffer
mov rsi,10
mov rdx, 1
mov rax,0
call fgets
pop rbp ; Pop stack
mov rax,0 ; Exit code 0
ret ; Return
I want to read something from the stdin which is a maximum of 10 characters long.
A FILE * is not a file descriptor. Instead of passing 1 like you do, pass [stdin] (this works because stdin is a global pointer in glibc, and the keyword stdin in NASM is a pointer to that):
mov rdx, [stdin]
If you use GAS, this will work:
mov stdin, %rdx
However, you should probably be using RIP-relative addressing; this allows your executable to be relocated and is required for PIE (position-independent executable)s, which are the default now. In NASM, simply put this at the top of the file:
default rel
In GAS, it's a bit more complicated. You have to add (%rip) to all the external symbols you use, like this:
mov stdin(%rip), %rdx
This loads the memory located at stdin (which is the 8-byte FILE * pointer you're looking for) into rdx.
fgets takes a FILE * pointer arg, not an integer file descriptor, as the 3rd arg.
Also, 1 is the stdout file descriptor, not stdin. But anyway, when fgets dereferences 1 as a pointer it segfaults. You could have seen this using a debugger to find the instruction that faulted.
In C you'd call fgets(buf, len, stdin). The global variable stdin is a pointer of type FILE *stdin. This pointer value (to an opaque FILE struct) is itself stored in memory at the symbol address stdin. (Glibc's startup code initializes this pointer to point at a FILE struct it allocates).
Therefore you want to load a qword pointer from static storage as the 3rd arg for fgets. You could have seen this yourself by looking at compiler output for the C function.
default rel ; Use RIP-relative addressing by default
extern stdin
extern fgets
...
main:
push rax ; dummy push to realign the stack by 16
...
lea rdi, [buffer] ; RIP-relative LEA, or mov edi, buffer in a non-PIE
mov esi, buffer.len
mov rdx, [stdin]
call fgets
...
pop rcx ; dummy pop to readjust RSP
ret
section .bss
buffer: resb 11 ; reserve 11 bytes, zero-filled
.len equ $ - buffer
Note that it's not just lea rdx, [stdin] or mov edx, stdin to put the address of the stdin variable itself in a register. fgets takes the pointer arg by value, not as a pointer to a pointer, and it's not FILE stdin, it's FILE *stdin.
I got the assembler to calculate the length of buffer for me. I filled it with zero bytes instead of ASCII '0' characters.
fgets() reads at most len-1 bytes, and writes a terminating \0 after those, so you need to pass 11 (the size of the full buffer) to read up to 10 bytes.
fgets isn't variadic so you don't need to zero AL before calling it.
I'm learning how to use __asm__ volatile in GCC and came up with a problem. I want implement a function performing atomic compare and exchange and returning the value that was previously stored in the destination.
Why does an "=a"(expected) output constraint work, but an "=r"(expected) constraint lets the compiler generate code that doesn't work?
Case 1.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
uint64_t atomic_cas(uint64_t * destination, uint64_t expected, uint64_t value){
__asm__ volatile (
"lock cmpxchgq %3, %1":
"=a" (expected) :
"m" (*destination), "a" (expected), "r" (value) :
"memory"
);
return expected;
}
int main(void){
uint64_t v1 = 10;
uint64_t result = atomic_cas(&v1, 10, 5);
printf("%" PRIu64 "\n", result); //prints 10, the value before, OK
printf("%" PRIu64 "\n", v1); //prints 5, the new value, OK
}
It works as expected. Now consider the following case:
Case 2.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
uint64_t atomic_cas(uint64_t * destination, uint64_t expected, uint64_t value){
__asm__ volatile (
"lock cmpxchgq %3, %1":
"=r" (expected) ://<----- I changed a with r and expected GCC understood it from the inputs
"m" (*destination), "a" (expected), "r" (value) :
"memory"
);
return expected;
}
int main(void){
uint64_t v1 = 10;
uint64_t result = atomic_cas(&v1, 10, 5);
printf("%" PRIu64 "\n", result); //prints 5, wrong
printf("%" PRIu64 "\n", v1); //prints 5, the new value, OK
}
I examined generated assembly and noticed the following things:
I. In both of the cases the function code is the same and looks as
0x0000555555554760 <+0>: mov rax,rsi
0x0000555555554763 <+3>: lock cmpxchg QWORD PTR [rdi],rdx
0x0000555555554768 <+8>: ret
II. The problem came when GCC inlined the atomic_cas so in the later case the correct value was not passed to the printf function. Here is the related fragment of disas main:
0x00000000000005f6 <+38>: lock cmpxchg QWORD PTR [rsp],rdx
0x00000000000005fc <+44>: lea rsi,[rip+0x1f1] # 0x7f4
0x0000000000000603 <+51>: mov rdx,rax ; <-----This instruction is absent in the Case 2.
0x0000000000000606 <+54>: mov edi,0x1
0x000000000000060b <+59>: xor eax,eax
QUESTION: Why does the replacing rax(a) with an arbitrary register (r) produce wrong result? I expected it worked in both of the cases?
UPD. I compile with the following flags -Wl,-z,lazy -Warray-bounds -Wextra -Wall -g3 -O3
The cmpxchg instruction always puts the result in the rax register. So you need to use the a constraint to tell GCC to move from that register. In case 2, you tell GCC to use an arbitrary register instead by using r, but you don't put anything in that register.
If you want to use r, you'll have to add a mov instruction to move the result from rax to that register (movq %%rax, %0). You'd also have to tell GCC that the rax register is changed by the instruction, for example by adding it to the "clobbers" section of the asm statement. For your case, there isn't a reason to complicate things in this manner.
First of all, https://gcc.gnu.org/wiki/DontUseInlineAsm. There is basically zero reason to roll your own CAS, vs. using bool __atomic_compare_exchange(type *ptr, type *expected, type *desired, bool weak, int success_memorder, int failure_memorder) https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html. This works even on non-_Atomic variables.
"=r" tells gcc it can ask for the output in whatever register it wants, so it can avoid having to mov the result there itself. (Like here where GCC wants the output in RSI as an arg for printf). And/or so it can avoid destroying the input it put in the same register. That's the entire point of =r instead of specific-register constraints.
If you want to tell GCC that the register it picks for input is also the output register, use "+r". Or in this case since you need it to pick RAX, use "+a"(expected).
There's already syntax for making the compiler pick the same register for 2 constraints with separate variables for input and output, specifically matching constraints: "=r"(outvar) : "0"(invar).
It would be a missed optimization if the syntax didn't let you describe a non-destructive instruction that could produce output in a different register from the input(s).
You can see what GCC actually picked by using the constraint in a comment.
Remember that GNU C inline asm is just text substitution into your template. The compiler literally has no idea what the asm instructions do, and doesn't even check they're valid. (That only happens when the assembler reads the compiler output).
...
asm volatile (
"lock cmpxchgq %3, %1 # 0 out: %0 | 2 in: %2"
: ...
...
The resulting asm shows the problem very clearly (Godbolt GCC7.4):
lock cmpxchgq %rsi, (%rsp) # 0 out: %rsi | 2 in: %rax
leaq .LC0(%rip), %rdi
xorl %eax, %eax
call printf#PLT
(I used AT&T syntax so your cmpxchgq %reg,mem would match the mem,reg operand order documented by Intel, although both GAS and clang's built-in assembler seem to accept it in the other order, too. Also because of the operand-size suffix)
GCC takes the opportunity to ask for the "=r"(expected) output in RSI as an arg for printf. Your bug is that your template makes a wrong assumption that %0 will expand to rax.
There are lots of examples of the lack of implicit connection between input and output that happen to use the same C var. For example, you can swap 2 C variables with an empty asm statement, just using constraints. How to write a short block of inline gnu extended assembly to swap the values of two integer variables?
This question already has an answer here:
Calling printf in extended inline ASM
(1 answer)
Closed 3 years ago.
I am trying to print AAAA using c __asm__ as following:
#include <stdio.h>
int main()
{
__asm__("sub $0x150, %rsp\n\t"
"mov $0x0,%rax\n\t"
"lea -0x140(%rbp), %rax\n\t"
"movl $0x41414141,(%rax)\n\t"
"movb $0x0, 0x4(%rax)\n\t"
"lea -0x140(%rbp), %rax\n\t"
"mov %rax, %rdi\n\t"
"call printf\n\t");
return 0;
}
Disassembly:
Dump of assembler code for function main:
0x0000000000400536 <+0>: push %rbp
0x0000000000400537 <+1>: mov %rsp,%rbp
0x000000000040053a <+4>: sub $0x150,%rsp
0x0000000000400541 <+11>: mov $0x0,%rax
0x0000000000400548 <+18>: lea -0x140(%rbp),%rax
0x000000000040054f <+25>: movl $0x41414141,(%rax)
0x0000000000400555 <+31>: movb $0x0,0x4(%rax)
0x0000000000400559 <+35>: lea -0x140(%rbp),%rax
0x0000000000400560 <+42>: mov %rax,%rdi
0x0000000000400563 <+45>: callq 0x400410 <printf#plt>
0x0000000000400568 <+50>: mov $0x0,%eax
0x000000000040056d <+55>: pop %rbp
0x000000000040056e <+56>: retq
End of assembler dump.
While running the code, there are basically two issues. #1 that it does not print "AAAA", #1 that when the RIP reaches retq, it throws segmentation fault
is there anything I am missing?
Your code has the following problems:
The main problem of your code is that you fail to restore the stack pointer to its previous value after calling printf. The compiler does not know that you modified the stack pointer and tries to return to whatever address is at (%rsp), crashing your program. To fix this, restore rsp to the value it had at the beginning of the asm statement.
you forgot to set up al with 0 to indicate that no floating point values are being passed to printf. This is needed as printf is a variadic function. While it doesn't cause any problems to set al to a value too high (and all values are less than or equal to 0), it is still good practice to set up al correctly. To fix this, set al to 0 before calling printf.
That said, you cannot safely assume that the compiler is going to set up a base pointer. To fix this, make sure to only reference rsp instead of rbp or use extended asm to let the compiler figure this out.
take care not to overwrite the stack. Note that the 128 bytes below rsp are called the red zone and must be preserved, too. This is fine in your code as you allocate enough stack space to avoid this issue.
your code tacitly assumes that the stack is aligned to a multiple of 16 bytes on entry to the asm statement. This too is an assumption you cannot make. To fix this, align the stack pointer to a multiple of 16 bytes before calling printf.
you overwrite a bunch of registers; apart from rax and rdi, the call to printf may overwrite any caller-saved register. The compiler does not know that you did so and may assume that all registers kept the value they had before. To fix this, declare an appropriate clobber list or save and restore all registers you plan to overwrite, including all caller-saved registers.
So TL;DR: don't call functions from inline assembly and don't use inline assembly as a learning tool! It's very hard to get right and does not teach you anything useful about assembly programming.
Example in Pure Assembly
Here is how I would write your code in normal assembly. This is what I suggest you to do:
.section .rodata # enter the read-only data section
str: .string "AAAA" # and place the string we want to print there
.section .text # enter the text section
.global main # make main visible to the link editor
main: push %rbp # establish...
mov %rsp, %rbp # ...a stack frame (and align rsp to 16 bytes)
lea str(%rip), %rdi # load the effective address of str to rdi
xor %al, %al # tell printf we have no floating point args
call printf # call printf(str)
leave # tear down the stack frame
ret # return
Example in Inline Assembly
Here is how you could call a function in inline assembly. Understand that you should never ever do this. Not even for educational purposes. It's just terrible to do this. If you want to call a function in C, do it in C code, not inline assembly.
That said, you could do something like this. Note that we use extended assembly to make our life a lot easier:
int main(void)
{
char fpargs = 0; /* dummy variable: no fp arguments */
const char *str = "AAAA";/* the string we want to print */
__asm__ volatile ( /* volatile means we do more than just
returning a result and ban the compiler
from optimising our asm statement away */
"mov %%rsp, %%rbx;" /* save the stack pointer */
"and $~0xf, %%rsp;" /* align stack to 16 bytes */
"sub $128, %%rsp;" /* skip red zone */
"call printf;" /* do the actual function call */
"mov %%rbx, %%rsp" /* restore the stack pointer */
: /* (pseudo) output operands: */
"+a"(fpargs), /* al must be 0 (no FP arguments) */
"+D"(str) /* rdi contains pointer to string "AAAA" */
: /* input operands: none */
: /* clobber list */
"rsi", "rdx", /* all other registers... */
"rcx", "r8", "r9", /* ...the function printf... */
"r10", "r11", /* ...is allowed to overwrite */
"rbx", /* and rbx which we use for scratch space */
"cc", /* and flags */
"memory"); /* and arbitrary memory regions */
return (0); /* wrap this up */
}
This question already has answers here:
How to invoke a system call via syscall or sysenter in inline assembly?
(2 answers)
Closed 3 years ago.
I'm trying to use inline assembly...
I read this page http://www.codeproject.com/KB/cpp/edujini_inline_asm.aspx but I can't understand the parameters passing to my function.
I'm writing a C write example.. this is my function header:
write2(char *str, int len){
}
And this is my assembly code:
global write2
write2:
push ebp
mov ebp, esp
mov eax, 4 ;sys_write
mov ebx, 1 ;stdout
mov ecx, [ebp+8] ;string pointer
mov edx, [ebp+12] ;string size
int 0x80 ;syscall
leave
ret
What do I have to do pass that code to the C function... I'm doing something like this:
write2(char *str, int len){
asm ( "movl 4, %%eax;"
"movl 1, %%ebx;"
"mov %1, %%ecx;"
//"mov %2, %%edx;"
"int 0x80;"
:
: "a" (str), "b" (len)
);
}
That's because I don't have an output variable, so how do I handle that?
Also, with this code:
global main
main:
mov ebx, 5866 ;PID
mov ecx, 9 ;SIGKILL
mov eax, 37 ;sys_kill
int 0x80 ;interruption
ret
How can I put that code inline in my code.. so I can ask for the pid to the user.. like this..
This is my precode
void killp(int pid){
asm ( "mov %1, %%ebx;"
"mov 9, %%ecx;"
"mov 37, %%eax;"
:
: "a" (pid) /* optional */
);
}
Well, you don't say specifically, but by your post, it appears like you're using gcc and its inline asm with constraints syntax (other C compilers have very different inline syntax). That said, you probably need to use AT&T assembler syntax rather than Intel, as that's what gets used with gcc.
So with the above said, lets look at your write2 function. First, you don't want to create a stack frame, as gcc will create one, so if you create one in the asm code, you'll end up with two frames, and things will probably get very confused. Second, since gcc is laying out the stack frame, you can't access vars with "[ebp + offset]" as you don't know how it's being laid out.
That's what the constraints are for -- you say what kind of place you want gcc to put the value (any register, memory, specific register) and the use "%X" in the asm code. Finally, if you use explicit registers in the asm code, you need to list them in the 3rd section (after the input constraints) so gcc knows you are using them. Otherwise it might put some important value in one of those registers, and you'd clobber that value.
You also need to tell the compiler that inline asm will or might read from or write to memory pointed-to by the input operands; that is not implied.
So with all that, your write2 function looks like:
void write2(char *str, int len) {
__asm__ volatile (
"movl $4, %%eax;" // SYS_write
"movl $1, %%ebx;" // file descriptor = stdout_fd
"movl %0, %%ecx;"
"movl %1, %%edx;"
"int $0x80"
:: "g" (str), "g" (len) // input values we MOV from
: "eax", "ebx", "ecx", "edx", // registers we destroy
"memory" // memory has to be in sync so we can read it
);
}
Note the AT&T syntax -- src, dest rather than dest, src and % before the register name.
Now this will work, but its inefficient as it will contain lots of extra movs. In general, you should NEVER use mov instructions or explicit registers in asm code, as you're much better off using constraints to say where you want things and let the compiler ensure that they're there. That way, the optimizer can probably get rid of most of the movs, particularly if it inlines the function (which it will do if you specify -O3). Conveniently, the i386 machine model has constraints for specific registers, so you can instead do:
void write2(char *str, int len) {
__asm__ volatile (
"movl $4, %%eax;"
"movl $1, %%ebx;"
"int $0x80"
:: "c" (str), /* c constraint tells the compiler to put str in ecx */
"d" (len) /* d constraint tells the compiler to put len in edx */
: "eax", "ebx", "memory");
}
or even better
// UNSAFE: destroys EAX (with return value) without telling the compiler
void write2(char *str, int len) {
__asm__ volatile ("int $0x80"
:: "a" (4), "b" (1), "c" (str), "d" (len)
: "memory");
}
Note also the use of volatile which is needed to tell the compiler that this can't be eliminated as dead even though its outputs (of which there are none) are not used. (asm with no output operands is already implicitly volatile, but making it explicit doesn't hurt when the real purpose isn't to calculate something; it's for a side effect like a system call.)
edit
One final note -- this function is doing a write system call, which does return a value in eax -- either the number of bytes written or an error code. So you can get that with an output constraint:
int write2(const char *str, int len) {
__asm__ volatile ("int $0x80"
: "=a" (len)
: "a" (4), "b" (1), "c" (str), "d" (len),
"m"( *(const char (*)[])str ) // "dummy" input instead of memory clobber
);
return len;
}
All system calls return in EAX. Values from -4095 to -1 (inclusive) are negative errno codes, other values are non-errors. (This applies globally to all Linux system calls).
If you're writing a generic system-call wrapper, you probably need a "memory" clobber because different system calls have different pointer operands, and might be inputs or outputs. See https://godbolt.org/z/GOXBue for an example that breaks if you leave it out, and this answer for more details about dummy memory inputs/outputs.
With this output operand, you need the explicit volatile -- exactly one write system call per time the asm statement "runs" in the source. Otherwise the compiler is allowed to assume that it exists only to compute its return value, and can eliminate repeated calls with the same input instead of writing multiple lines. (Or remove it entirely if you didn't check the return value.)