Using write() to output string on stack
I suggest reading What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64. You need the system call number in eax
and the arguments in ebx
, ecx
, edx
, esi
, edi
, ebp
in that order. write
takes three arguments so you need:
system call number (4) in
eax
file descriptor (1 for standard output) in
ebx
pointer to buffer (
esp
) inecx
, this you have rightcount of bytes to be written (11, since there is no reason to write the null byte) in
edx
. (By value, not a pointer to the length:write
lets you know how many bytes were actually written via the return value ineax
, not an output arg.)
There is no need to put anything else on the stack. The kernel doesn't look for arguments there, everything is in registers. This is a difference from the normal function calling convention.
Also, you probably want to invoke the exit
system call afterwards, or your program will run off the end of its code and crash by executing garbage. That is system call number 1 and it takes one argument in ebx
which is an exit code.
The following works:
section .text
global _start
_start:
push 0x00646c72
push 0x6f77206f
push 0x6c6c6568
mov ecx, esp
mov edx, 11
mov ebx, 1
mov eax, 4
int 0x80
mov eax, 1
xor ebx, ebx
int 0x80
Linux write sys call using string on stack
Your syscall numbers seem WAY off.
From your use of movq
and the "r" registers, I can guess you are trying on x86-64. Taking a look at /usr/include/asm/unistd_64.h
, I can see the following:
#define __NR_write 1
#define __NR_stat 4
#define __NR_exit 60
strace
agrees with me:
$ strace ./abc
execve("./abc", ["./abc"], [/* 43 vars */]) = 0
stat("", NULL) = -1 EFAULT (Bad address)
write(-1698988341, NULL, 3 <unfinished ... exit status 0>
Note that the parameters are also way off. You are also using the wrong registers for the rest of the parameters. The calling convention on x86-64, AFAIK, uses the following registers for the parameters, in this order: rdi
, rsi
, rdx
, r10
, r8
, r9
.
Perhaps you are trying to do syscalls on x86-64 the way they are done on i386 and expecting it to be the same?
How to use the write system call using C langauge in Linux?
- You need to ensure that
open
succeeded instead of blindly writing to the file-descriptor.- Always check the return value of a syscall (and most C standard library functions) and check
errno
if the return value indicated an error.
- Always check the return value of a syscall (and most C standard library functions) and check
- Your string literal will include a hidden
\0
(NULL
) character after the dot.- Writing
text
directly to the file will therefore include the trailing\0
which is what you're seeing.
- Writing
These issues can be rectified by:
Always checking the return value of a syscall - and in this case: print a helpful error message to stdout and perform any necessary cleanup (the
goto closeFile;
statement).- Because C doesn't have a native
try/catch
or RAII it means its difficult to write terse error-handling and cleanup code, but usinggoto
for common clean-up code is generally acceptable in C, hence thegoto closeFile
statement.
- Because C doesn't have a native
Using
strlen
to get the actual length of the string.- Though in a pinch it's okay to use
sizeof(text) - 1
provided you're in a scope where the C compiler knows the length oftext
as usingsizeof()
won't work if you cross a function boundary due to array pointer decay.
- Though in a pinch it's okay to use
Like so:
void writeToFile() {
int fd = open( "text1.txt", O_CREAT | O_WRONLY ); // Use `O_WRONLY` instead of `O_RDWR` if you're only writing to the file. Use `O_CREAT` to create a file if it doesn't already exist.
if( fd == -1 ) {
printf( "Error opening file: errno: %d - %s\n", errno, strerror( errno ) );
return;
}
size_t textLength = strlen( text );
size_t written = write( fd, text, textLength );
if( written == -1 ) {
printf( "Error writing text: errno: %d - %s\n", errno, strerror( errno ) );
goto closeFile;
}
else if( written < textLength ) {
printf( "Warning: Only %d of %d bytes were written.", written, textLength );
goto closeFile;
}
else {
// Carry on as normal.
}
closeFile:
if( close( fd ) == -1 ) {
printf( "Error closing file: errno: %d - %s\n", errno, strerror( errno ) );
}
}
Writing my own system call to count how many o's appear in string pointer
The correct way is to:
Pass pointer to string to the syscall
do a
strlen_user(string)
to find out the size of the stringProvide a buffer large enough to hold the string (for example with
kmalloc()
)use
copy_from_user()
to copy the string from userspace into a kernel buffer (check if the call succeeded!)Perform the substitution operation on the kernel buffer
use
copy_to_user()
to copy the string back to userspaceFree the buffer if you allocated one with
kmalloc()
.
You should not access userspace directly, since the kernel may oops if the memory pointed to by your pointer is not available (i.e. swapped out) or protected for read/write. This is for example the case in your program; trying to write to this memory directly (without copy_to_user) will result in a kernel page fault. Note : Direct access can work, but it is not safe!
Furthermore, you have to use strlen(string)
(or strlen(string)+1
to account for the terminating zero byte) when operating with strings.
adding a simple system call to linux kernel
Your code is executed in kernel context whilst the buffer with data comes from the userspace. If you need to process some string from the userspace, copy it to the kernel memory using strncpy_from_user() function. If you don't follow the scheme and simply try to access the data directly, this will lead to a memory access violation.
A better solution (based on your code) would look somewhat like this:
asmlinkage long sys_hello(char* name) {
long nb_symbols;
char *name_internal;
long i;
/*
* Estimate the buffer length sufficient
* to accommodate the string
*/
for (i = 1; ; ++i) {
nb_symbols = strnlen_user(name, i);
if (nb_symbols <= 0)
return -EFAULT;
if (nb_symbols < i)
break;
}
/* Allocate the storage */
name_internal = kmalloc(nb_symbols + 1, GFP_KERNEL);
if (name_internal == NULL)
return -ENOMEM;
if (strncpy_from_user(name_internal, name, nb_symbols + 1) !=
nb_symbols) {
kfree(name_internal);
return -EFAULT;
}
printk("The 'name' is '%s'\n", name_internal);
kfree(name_internal);
return 0;
}
However, please note that such a loop (as one in my example) might not be an acceptable solution for buffer length estimation. Ideally, you could drop it and use a static char
array of fixed length to use strncpy_from_user()
with.
Mixing syscall write with printf on linux
That's because stdio (the C standard I/O package) uses line buffering for stdout if stdout goes to a terminal. That means that data you write is not immediately sent to the terminal but rather buffered until a whole line is available. What you observe in your first program (hello on a line) is that no character of Hello
is actually written until you call printLetter
with a line feed, causing the buffer for stdout to be flushed to the terminal.
I see the following approaches to solve your problem (any single of them does the trick, but use only one approach):
- edit
ansiSetRed
etc. to callfwrite
instead of directly executing awrite
system call. This should make buffering work as expected. - call
setbuf(stdout, NULL)
to turn off buffering before writing any data. - write to
stderr
instead ofstdout
asstderr
is unbuffered - execute
fflush(stdout)
after eachprintf
to manually flushstdout
. - Rewrite
printLetter
to use the system callwrite
instead ofprintf
.
How to use the write() system call in C++?
c++ has its own input/output operation and for the output, the standard is std::cout
.
But there is a function names write
in c and also a write
that uses file descriptor : int
here is the man 2 write
WRITE(2) Linux Programmer's Manual WRITE(2)
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
a system call is implemented in the Linux Kernel
And the user has no access to it. But A library like standard C library
or like GNU C library
wraps system calls that others can use it easily.
See this page: https://www.kernel.org/doc/man-pages/
then go to
2: System calls documents the system calls provided by the Linux kernel.
then at the bottom you will find write(2)
and a simple example is:
int fd = open( "file", O_RDONLY );
if( fd == -1 )
{
perror( "open()" );
close( fd );
exit( 1 );
}
char buffer[ 100 ];
ssize_t read_byte;
if( ( read_byte = read( fd, buffer, 100 ) ) == -1 )
{
perror( "read()" );
close( fd );
exit( 1 );
}
if( write( STDOUT_FILENO, buffer, read_byte ) == -1 )
{
perror( "write()" );
close( fd );
exit( 1 );
}
close( fd );
and you should use this header files as well:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h> // exit()
#include <stdio.h> // for perror()
and these functions also is known as Low-Level I/O function
Thus depending on what level you are coding, then you should decide to use what functionality is best for you. I do not think c++ programmers use these level for a standard c++ code.
A simple screenshot of where is system-called
located.
source
Related Topics
When Should Xargs Be Preferred Over While-Read Loops
Determine If Relative or Absolute Path in Shell Program
Ubuntu Equivalent of Yum's Whatprovides, to Find Which Package Provides a File
Hook into Linux Key Event Handling
Lowest Latency Notification Method Between Process Under Linux
Coreos - Get Docker Container Name by Pid
Which Stack Is Used by Interrupt Handler - Linux
Rename Multiple Directories Matching Pattern
How to Repeat a Dash (Hyphen) in Shell
How to Get Debugging Symbols Working in Linux Perf Tool Inside Docker Containers
How to Change the Default Gcc Compiler in Ubuntu
List Files Recursively in Linux Cli With Path Relative to the Current Directory
How to Create a Hex Dump of File Containing Only the Hex Characters Without Spaces in Bash
What Is the Status of Posix Asynchronous I/O (Aio)
A Way to Determine a Process'S "Real" Memory Usage, I.E. Private Dirty Rss
Bash And/Or .Bashrc Not Working Properly After Su or Ssh Login Unless Run "Bash" Command