Skip to content
Snippets Groups Projects
Commit 14f4f6e5 authored by Peter Gerwinski's avatar Peter Gerwinski
Browse files

Beispiele und Notizen 15.5.2023

parent bccec181
Branches
No related tags found
No related merge requests found
Von "Hello, world!n" bis zum Kernel, 15.05.2023, 16:20:31
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
printf ("Hello, world!\n"); wird zu puts ("Hello, world!"); optimiert.
Quelltext von puts(): apt-get source glibc
cassini/home/peter/bo/2023ss/bs/20230515/glibc-2.28> find . -name "*puts*.c"
./gshadow/putsgent.c
./gshadow/tst-putsgent.c
./libio/iofputs.c
./libio/iofputs_u.c
./libio/ioputs.c
...
int _IO_puts (const char *str)
weak_alias (_IO_puts, puts)
--> Information für den Linker:
Wenn ein Benutzerprogramm nach "puts()" fragt, gib ihm "_IO_puts()".
include/libc-symbols.h:
/* Define ALIASNAME as a weak alias for NAME.
If weak aliases are not available, this defines a strong alias. */
# define weak_alias(name, aliasname) _weak_alias (name, aliasname)
# define _weak_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((weak, alias (#name)));
"__attribute__ (( ... ));" ist ein spezieller GCC-Befehl.
Warum 2 Klammern auf und zu?
Wenn ein anderer Compiler diesen Befehl nicht kennt, machen wir:
#define __attribute__ (X)
Mit nur einem Klammerpaar müßten wir zwischen
#define __attribute__ (X)
#define __attribute__ (X,Y)
#define __attribute__ (X,Y,Z)
unterscheiden. Der Präprozessor kennt aber nur eine feste Anzahl von Parametern.
Warum "weak_alias"?
--> Vermeidung von Namenskonflikten.
Wenn das Benutzerprogramm selbst eine Funktion puts() bereitstellt,
hat diese Vorrang vor der in der glibc.
puts() ruft _IO_sputn() auf.
_IO_sputn() ist ein Makro:
libio/libioP.h:
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
Dies ist wahrscheinlich ein "Hook": Durch Umdefinieren des Makros
kann man auf einfache Weise die Funktion auf eine andere "umleiten".
Dieser ruft einen weiteren Makro auf:
libio/libioP.h:
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
Dieser ruft einen weiteren Makro auf:
libio/libioP.h:
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
Dieser ruft eine Funktion auf, verwendet dabei einen weiteren Makro:
libio/libioP.h:
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
"vtable" ist eine virtuelle Methodentabelle.
_IO_puts() ist eine "Methode" der Datei (hier: stdout).
Vor dem Aufruf der Methode macht glibc eine Laufzeitüberprüfung der
virtuellen Methodentabelle.
libio/libioP.h enthält eine Typendeklaration struct _IO_jump_t
grep -ir " _IO_jump_t" *
libio/fileops.c:
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
/* Inhalt der vtable ... */
...
JUMP_INIT(overflow, _IO_file_overflow),
...
JUMP_INIT(xsputn, _IO_file_xsputn),
...
JUMP_INIT(write, _IO_new_file_write),
...
}
Funktion:
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
...
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)
Diese ruft gegen Ende eine weitere Funktion auf:
_IO_default_xsputn()
libio/genops.c:
_IO_default_xsputn (FILE *f, const void *data, size_t n)
Diese ruft u.a. einen Makro _IO_OVERFLOW auf.
Dort erfolgt das eigentliche Schreiben.
libio/libioP.h:
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
Dies ist also wieder der Aufruf einer virtuellen Methode.
libio/genops.c:
int
_IO_new_file_overflow (FILE *f, int ch)
{
...
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
Diese ruft u.a. _IO_do_write() auf.
Dort erfolgt das eigentliche Schreiben.
libio/genops.c:
versioned_symbol (libc, _IO_new_do_write, _IO_do_write, GLIBC_2_1);
int
_IO_new_do_write (FILE *fp, const char *data, size_t to_do)
{
return (to_do == 0
|| (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)
static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
new_do_write() ruft u.a. _IO_SYSWRITE() auf.
Dort erfolgt das eigentliche Schreiben.
libio/libioP.h:
#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)
libio/fileops.h:
ssize_t
_IO_new_file_write (FILE *f, const void *data, ssize_t n)
{
ssize_t to_do = n;
while (to_do > 0)
{
ssize_t count = (__builtin_expect (f->_flags2
& _IO_FLAGS2_NOTCANCEL, 0)
? __write_nocancel (f->_fileno, data, to_do)
: __write (f->_fileno, data, to_do));
if (count < 0)
{
f->_flags |= _IO_ERR_SEEN;
break;
}
to_do -= count;
data = (void *) ((char *) data + count);
}
n -= to_do;
if (f->_offset >= 0)
f->_offset += n;
return n;
}
grep -r __write * liefert sehr viele Implementationen von __write()
für verschiedenste Unix-Kernel (u.a. Hurd, linux, ...).
--> Ab jetzt ist der Quelltext plattformabhängig.
sysdeps/unix/sysv/linux/write.c:
ssize_t
__libc_write (int fd, const void *buf, size_t nbytes)
{
return SYSCALL_CANCEL (write, fd, buf, nbytes);
}
libc_hidden_def (__libc_write)
weak_alias (__libc_write, __write)
sysdeps/unix/sysdep.h:
#define SYSCALL_CANCEL(...) \
({ \
long int sc_ret; \
if (SINGLE_THREAD_P) \
sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__); \
else \
{ \
int sc_cancel_oldtype = LIBC_CANCEL_ASYNC (); \
sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__); \
LIBC_CANCEL_RESET (sc_cancel_oldtype); \
} \
sc_ret; \
})
#define INLINE_SYSCALL_CALL(...) \
__INLINE_SYSCALL_DISP (__INLINE_SYSCALL, __VA_ARGS__)
#define __INLINE_SYSCALL_DISP(b,...) \
__SYSCALL_CONCAT (b,__INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__)
Der Parameter "b" ist "__INLINE_SYSCALL". Dieser ist neu hinzugekommen.
Wir rufen demnach "__INLINE_SYSCALL<n>" auf, wobei <n> für die Anzahl der
Parameter des Syscalls steht.
#define __INLINE_SYSCALL0(name) \
INLINE_SYSCALL (name, 0)
#define __INLINE_SYSCALL1(name, a1) \
INLINE_SYSCALL (name, 1, a1)
#define __INLINE_SYSCALL2(name, a1, a2) \
INLINE_SYSCALL (name, 2, a1, a2)
...
#ifndef INLINE_SYSCALL
#define INLINE_SYSCALL(name, nr, args...) __syscall_##name (args)
#endif
--> Hier erfolgt der Aufruf der Funktion __syscall_write (fd, buf, nbytes).
sysdeps/unix/syscalls.list: Liste aller Systemaufrufe mit Namen, Parametern
sowie dem Namen der Funktion innerhalb der glibc.
Daraus werden dann automatisch die Funktionen generiert.
sysdeps/unix/sysv/linux/x86_64/syscall.S
enthält ein Assembler-Template (".S" steht für Assembler mit Präprozessor)
für den eigentlichen Funktionsaufruf.
Hier also:
__syscall_write (fd, buf, nbytes)
%rdi: Nummer des Syscalls (hier: 1(?) für "write")
%rsi, %rdx, %rcx, ...: Parameter des Syscalls (hier: fd, buf, nbytes)
syscall: ein spezieller Assembler-Befehl für den Aufruf einer Funktion im Kernel
--> Hardware-Unterstützung für den Übergang Kernel<-->Userspace
(früher auf x86-Prozessoren ab dem i386: Software-Interrupt 0x80)
Rückgabewert des Syscalls: %rax
cmpq $-4095, %rax /* Check %rax for error. */
jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. */
Der Sprung erfolgt, wenn %rax zwischen (einschließlich) -1 und -4095 liegt.
Siehe: https://stackoverflow.com/questions/21440403/what-does-the-cmpq-instruction-do
Vorschlag für die verbleibende Zeit der heutigen Lehrveranstaltung:
Einblick in die Benutzung eines Debuggers
anhand eines einfachen Programms.
"Nicht so etwas Schwieriges wie 'Hello, world!'" (ME)
#include <stdio.h>
int main (void)
{
for (int i = 10; i > 0; i--)
printf ("%d ... ", i);
printf ("Bumm.\n");
return 0;
}
#include <stdio.h>
int main (void)
{
for (int i = 10; i > 0; i--)
{
printf ("%d ... ", i);
fflush (stdout);
}
printf ("Bumm.\n");
return 0;
}
#include <stdio.h>
int main (void)
{
for (int i = 10; i > 0; i--)
printf (i, " ... ");
printf ("Bumm.\n");
return 0;
}
cassini/home/peter/bo/2023ss/bs/20230424> ls -l /dev/chardev
crw-rw-rw- 1 root root 241, 0 Apr 24 18:31 /dev/chardev
cassini/home/peter/bo/2023ss/bs/20230424> cat /dev/chardev
I already told you 10 times Hello world!
cassini/home/peter/bo/2023ss/bs/20230424> ls -l dev
insgesamt 0
crw-r--r-- 1 root root 241, 0 Apr 24 17:16 chardev
cassini/home/peter/bo/2023ss/bs/20230424> cat dev/chardev
I already told you 11 times Hello world!
cassini/home/peter/bo/2023ss/bs/20230424> mkdir -p ../20230515/dev
cassini/home/peter/bo/2023ss/bs/20230424> touch ../20230515/dev/chardev
cassini/home/peter/bo/2023ss/bs/20230424> ls -l ../20230515/dev/chardev
-rw-r--r-- 1 peter peter 0 Mai 15 15:56 ../20230515/dev/chardev
cassini/home/peter/bo/2023ss/bs/20230424> cat ../20230515/dev/chardev
cassini/home/peter/bo/2023ss/bs/20230424> cat ../20230424/dev/chardev
I already told you 12 times Hello world!
cassini/home/peter/bo/2023ss/bs/20230424>
#include <stdio.h>
int main (void)
{
printf ("Hello, world!\n");
return 0;
}
.file "hello-01.c"
.text
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello, world!"
.text
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc ; #include <stdio.h>
subq $8, %rsp ;
.cfi_def_cfa_offset 16 ; int main (void)
leaq .LC0(%rip), %rdi ; {
call puts@PLT ; printf ("Hello, world!\n");
movl $0, %eax ; return 0;
addq $8, %rsp ; }
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
#include <stdio.h>
int main (void)
{
puts ("Hello, world!");
return 0;
}
.file "hello-02.c"
.text
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello, world!"
.text
.globl main
.type main, @function
main:
.LFB11:
.cfi_startproc ; #include <stdio.h>
subq $8, %rsp ;
.cfi_def_cfa_offset 16 ; int main (void)
leaq .LC0(%rip), %rdi ; {
call puts@PLT ; puts ("Hello, world!");
movl $0, %eax ; return 0;
addq $8, %rsp ; }
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE11:
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
#include <unistd.h>
int main (void)
{
write (1, "Hello, world!\n", 14);
return 0;
}
.file "hello-03.c"
.text
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "Hello, world!\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $14, %edx
leaq .LC0(%rip), %rsi
movl $1, %edi
call write@PLT
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
cassini/home/peter/bo/2023ss/bs/20230424> ls -l /dev/sda*
brw-rw---- 1 root disk 8, 0 Apr 20 22:38 /dev/sda
brw-rw---- 1 root disk 8, 1 Apr 20 22:38 /dev/sda1
brw-rw---- 1 root disk 8, 2 Apr 20 22:38 /dev/sda2
brw-rw---- 1 root disk 8, 3 Apr 20 22:38 /dev/sda3
cassini/home/peter/bo/2023ss/bs/20230424>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment