Table of parts: https://docs.google.com/document/d/1IIOkivkS0lV9QIL16b_OjT480BxZHQfKswvvS-CVZlw/view
Syscalls
Системные вызовы
#include <unistd.h>
Manual, справочные страницы, руководство
some_syscall(2)
man 2 some_syscall
Typical manpage sections:
See man 1 man for the list of all typical sections.
Typical manpage paragraphs (for a syscall/function):
The environment you see after calling "man" is the "less" program. Some minimal controls:
(sudo apt-get install manpages-ru)
Force Russian language for a manpage (which may be old):
LANG=ru_RU.UTF-8 man 2 dup2
Force English manpage:
LANG=en_US.UTF-8 man 2 dup2
Find which flag of mount syscall should be used to make the resulting mount read-only. Which header file should be included in C in order for "mount" function to work?
Найдите какой флаг нужно указать системному вызову mount, чтобы результатирующая файловая системы была доступна только для чтения (read-only). Какой заголовочный файл нужно подключить, чтобы использовать функцию "mount" в Си?
There can be Linux-specific functions or flags to functions.
A term: wrapper (обёртка).
#include <stdio.h>
int main() {
const char* hello = "Hello, world\n";
// C way; portable everywhere
fwrite(hello, 1, 13, stdout);
// POSIX way; portable across POSIX
write(1, hello, 13);
// something in-between; with SYS_write instead of 4 should be portable across Linux.
syscall(4, 1, hello, 13);
// raw syscall without any wrapper; not portable (only on x86)
asm volatile ("\
mov $4, %%eax \n\
mov $1, %%ebx \n\
mov %[hhh], %%ecx \n\
mov $13, %%edx \n\
int $0x80 "
: : [hhh]""(hello) : "eax","ebx","ecx","edx");
return 0;
}
Classify the following functions in three groups (listed above):
Классифицируйте следующие функции на три группы (доступно в Си, доступно в POSIX, доступно только в Linux):
int open(const char* pathname, int flags, mode_t mode);
#include <errno.h>
-1 is an error; errno is error number
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("lala.txt", O_WRONLY|O_CREAT);
if (fd==-1) { perror("open"); } // errno, #include <errno.h>
fprintf(stderr, "File is opened, fd=%d\n", fd);
int ret = write(fd, "Lol\n", 4);
fprintf(stderr, "Write is finished, %d bytes written\n", ret);
return 0;
}
Similar program in standard C:
#include <stdio.h>
int main() {
FILE* f = fopen("lala.txt", "w");
if (!f) { perror("fopen"); return 1; }
fwrite("Lol\n", 1, 4, f);
return 0;
}
Historically files were limited by 2-4 GB. This causes "technical debt" now in some cases.
#define _FILE_OFFSET_BITS 64
(see feature_test_macros(7) for the list of such things)
Add closing the file
Make a program that opens file "bmm33.txt" for reading, reads 5 bytes from it into a character array, then opens file "apop4.txt" for appending and writes those 5 bytes to it, then closes all files.
Напишите программу, которая открывает файл "bmm33.txt" на чтение, читает 5 байт из него в массив символов, затем открывает файл "apop4.txt" на дополнение (записать в конец файла) и записывает эти 5 байт туда, затем закрывает все файлы.
ssize_t read(int fd, void *buf, size_t count);
Can return:
Safe writing of a block of memory to a fd:
const char* buf = "12345\n";
size_t remaining_bytes = 6;
int cursor = 0;
while(remaining_bytes > 0) {
int bytes_written = write(fd, buf + cursor, remaining_bytes);
if (bytes_written == -1) {
if (errno == EINTR || errno == EAGAIN) continue;
// ERROR
}
if (bytes_written == 0) {
// ?
}
remaining_bytes -= bytes_written;
cursor += bytes_written;
}
Using fwrite avoid necessity of handling short writes.
Implement a program that exchanges two files of equal lengths in place without using any additional files, memory array or changing inode number.
Реализуйте программу, которая меняет местами содержимое двух файлов без использования временного файла, массива или смены inode файлов.
(system call "lseek")
┌─────────────────┐ ┌─────────────────────────┐ ┌────────────┐
│ File descriptor │ ──> │ Opened file with cursor │ ──> │ inode/data │
└─────────────────┘ └─────────────────────────┘ └────────────┘
fd1 = open("file", …);
fd2 = open("file", …);
fd3 = dup2(fd2, 10);
$ strace ./qq
execve("./qq", ["./qq"], [/* 26 vars */]) = 0
open("lala.txt", O_RDWR|O_LARGEFILE) = 3
writev(2, [{"File is opened, fd=3\n", 21}, {NULL, 0}], 2File is opened, fd=3
) = 21
read(3, "L", 1) = 1
write(3, "\n", 1) = 1
read(3, "l", 1) = 1
write(3, "\n", 1) = 1
exit_group(0) = ?
Start some your program in strace. Associate syscalls with lines in your program.
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
struct timeval {
time_t tv_sec;
suseconds_t tv_usec;
};
Measure using gettimeofday(2) or clock_gettime(2) how long it takes to call getpid(2) one million times. Start your program using "time" command: "time ./myprogram". Now measure how long it takes to perform a qsort(3) on an already sorted array of 32 int elements one million times.
Измерьте при помощи gettimeofday(2) или clock_gettime(2) сколько времени занимают миллион вызовов функции getpid(2). Запустите программу при помощи команды "time". Теперь измерьте сколько времени занимает сделать qsort(3) на уже отсортированном массиве миллион раз.
Processes
pid_t fork();
pid_t wait(int *status);
Write a program that does fork and waits for the child to terminate. Both parent and child should output their PIDs, see getpid(2).
Напишите программу, которая создаёт дочерний процесс и ждёт его завершения. И родитель, и дочерний процесс должны выводить свой идентификатор процесса (смотрите getpid(2)).
int execve(const char* filename, char *const argv[], char *const envp[]);
There are wrappers, see execv(3).
$ readelf -a /bin/bash | grep interpreter
[Requesting program interpreter: /lib/ld-linux.so.2]
Write a shebang (#!) header to the bash script you have saved in the previous part, to make it runnable by "./myscript". You can find an examples of such header using the command below.
Допишите заголовок скрипта со ссылкой на интерпретатор (#!) в сохранённый вами в предыдущей части скрипт, чтобы его можно было запускать как программу, через ./myscript. Примеры такого заголовка можно найти, выполнив команду, приведённую ниже:
grep '#!' /usr/bin/*
Q: How many command line arguments has "grep" program in this command line?
Start your "args" (or other) program manually calling dynamic linker/loader
Write a program employing execve syscall. For environment variables use environ(7).
Write a shell. It should read commands from stdin and start appropriate processes. Line can be split into argv using strtok(3) or using C++ means.
Напишите оболочку. Она должна читать команды из стандартного входа и запускать соответствующие процессы. Строку можно разбивать на аргументы командной строки при помощи strtok(3) или средствами C++.
Implement file redirection in your shell.
Реализуйте перенаправление в/из файлов в вашей оболочке.
Use system call dup2(2).
Implement pipelines in your shell.
Реализуйте конвейеры в вашей оболочке.
Use system call pipe(2).
Implement starting shell scripts (reading input commands from file) and executing commands from command line (-c command line argument).
Реализуйте запуск скриптов оболочки (чтения команд из файла) и выполнение команд из аргумента командной строки (-c).
./myshell # usual (interactive mode) обычный (интерактивный) режим
./myshell file # script скрипт
./myshell -c "something" # command line из командной строки
(including the program name)
cat /proc/<pid>/cmdline | tr '\0' ' '; echo
cat /proc/<pid>/environ | tr '\0' '\n'
Start the "copy file loop" script (make it open the shell). Find from other tab (using ps(1) and grep(1)) PID of the bash running the script. Using /proc, tell what file descriptors has it opened, what command line parameters does it have, what environment variables and what it's current directory.
Запустите скрипт, которые в цикле пытается скопировать файл (чтобы он открыл дочернюю оболочку). Найдите из другой вкладки (используя ps(1) и grep(1)) идентификатор процесса bash, который исполняет этот скрипт. Используя /proc, cкажите какие открыты файловые дескрипторы, какие переданы параметры командной строки, какие переменные окружения и какой у него текущий каталог.
Package manager
apt-get install another-useless-program
gdebi somefile.deb
dpkg -i somefile.deb
/var/cache/apt/archives
/etc/apt/sources.list[.d/]
apt-cache search something
apt-get update
apt-file update; apt-file search
apt-get upgrade
dpkg -L mypackage
dpkg -I somefile.deb
dpkg -c somefile.deb
apt-cache show mypackage
apt-cache policy mypackage
# apt-get update
...
Get:20 http://ftp.mgts.by testing/contrib i386 Packages/DiffIndex [27.8 kB]
Get:21 http://ftp.mgts.by testing/contrib Translation-en/DiffIndex [27.8 kB]
Get:22 http://ftp.mgts.by testing/main Translation-en/DiffIndex [27.9 kB]
Hit http://ftp.mgts.by jessie Release.gpg
...
Fetched 2,678 kB in 25s (106 kB/s)
Reading package lists... 33%
Reading package lists... Done
# apt-get install python-pil
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
python-pil.imagetk
Suggested packages:
python-pil-doc python-pil-dbg python-pil.imagetk-dbg
The following packages will be upgraded:
python-pil python-pil.imagetk
2 upgraded, 0 newly installed, 0 to remove and 18 not upgraded.
Need to get 318 kB of archives.
After this operation, 0 B of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://security.debian.org/ jessie/updates/main python-pil.imagetk amd64 2.6.1-2+deb8u3 [13.8 kB]
Get:2 http://security.debian.org/ jessie/updates/main python-pil amd64 2.6.1-2+deb8u3 [304 kB]
Fetched 318 kB in 0s (373 kB/s)
(Reading database ... 739003 files and directories currently installed.)
Preparing to unpack .../python-pil.imagetk_2.6.1-2+deb8u3_amd64.deb …
Unpacking python-pil.imagetk:amd64 (2.6.1-2+deb8u3) over (2.6.1-2+deb8u2) …
Preparing to unpack .../python-pil_2.6.1-2+deb8u3_amd64.deb …
Unpacking python-pil:amd64 (2.6.1-2+deb8u3) over (2.6.1-2+deb8u2) …
Setting up python-pil:amd64 (2.6.1-2+deb8u3) …
Setting up python-pil.imagetk:amd64 (2.6.1-2+deb8u3) ...
# apt-cache policy python-pil
python-pil:
Installed: 2.6.1-2+deb8u3
Candidate: 2.6.1-2+deb8u3
Version table:
3.4.2-1 0
10 http://ftp.mgts.by/debian/ testing/main amd64 Packages
3.4.2-1~bpo8+1 0
100 http://http.debian.net/debian/ jessie-backports/main amd64 Packages
*** 2.6.1-2+deb8u3 0
900 http://security.debian.org/ jessie/updates/main amd64 Packages
100 /var/lib/dpkg/status
2.6.1-2+deb8u2 0
900 http://ftp.mgts.by/debian/ jessie/main amd64 Packages
# apt-cache show python-pil=2.6.1-2+deb8u3
Package: python-pil
Version: 2.6.1-2+deb8u3
Installed-Size: 1195
Architecture: amd64
Replaces: python-imaging (<< 1.1.7+2.0.0-1.1)
Provides: python-pillow, python2.7-pil
Depends: python (<< 2.8), python (>= 2.7~), python:any (>= 2.7.5-5~), mime-support | python-pil.imagetk, libc6 (>= 2.14), libfreetype6 (>= 2.2.1), libjpeg62-turbo (>= 1.3.1), liblcms2-2 (>= 2.2+git20110628), libtiff5 (>= 4.0.3), libwebp5, libwebpdemux1, libwebpmux1, zlib1g (>= 1:1.1.4)
Suggests: python-pil-doc, python-pil-dbg
Breaks: python-imaging (<< 1.1.7+2.0.0-1.1)
Description-en: Python Imaging Library (Pillow fork)
.....
Install some package using apt-get install
Users and permissions
Избирательное управление доступом
Права на чтение, запись, выполнение.
Create two new users on your system from command line. Add yourself to the first new user's associated group.
Создайте в системе ещё двух пользователей, из командной строки. Добавьте себя в группу первого нового пользователя.
From you primary use account, create the following:
Try accessing those files from the new user account.
Из своей основной учётной записи, создайте:
Проверьте как работает контроль доступа из учётной записи другого пользователя.
More command line tools
find <directory> <options> <commands>
find .
find . -ls
find /home/mydir -printf "%i %P\n"
# warning: the next command tried to delete files:
find / -xdev -maxdepth 5 -iname '*.jpg' -delete
find . -mindepth 1 -maxdepth 1 -print0 | xargs -0 file
Find all regular files in current directory that are more than 3 kilobytes in size.
Найдите все обычные файлы в текущей директории, размер которых больше 3 килобайт.
In all files with names matching "*.cpp" or "*.h" patterns, find lines containing word "include". Use find(1), xargs(1) and grep(1). Check handling the spaces in file names.
Во всех файлах, имена которых соответствуют маске *.cpp или *.h, найдите строки, содержащие слово "include". Проверьте как обрабатываются файлы с пробелами в имени.
Find out and present what sort(1) and uniq(1) does.
Исследуйте и расскажите что делают команды sort(1) и uniq(1).
#!/bin/bash
exec /use/bin/some_program "$@"
Wrong solutions:
Make a shell script wrapper for your "args" program. It should log (append, >>) the current date(1) to a file before starting the program.
Напишите скрипт-обёртку для вашей программы args. Он должен протоколировать (дополнять в конец файла, >>) текущую дату ( date(1) ) в файл перед запуском программы.
Makefiles
all: myprogram
CFLAGS=-Wall
LDFLAGS=
myprogram: myprogram.o
gcc $(CFLAGS) myprogram.o -o myprogram
myprogram.o: myprogram.c
gcc ${LDFLAGS} -c myprogram.c -o myprogram.o
VARIABLE=value
target: dependencies
<hard tab> commands
ПЕРЕМЕННАЯ=значение
цель: зависимости
<жёсткая табуляция> команды
Variable expansion sources:
$ make myprogram CFLAGS=-Werror
CFLAGS=-Wall
$ CFLAGS=-Werror make myprogram
Nested expansion trick
GNU make has proper "if" construct. But it can also be emulated with nested variable expansion:
PROFILE=debug
CFLAGS-debug=-Wall
CFLAGS-debug+=-ggdb
CFLAGS-debug+=-DDEBUG=1
CFLAGS-release=-O3
CFLAGS-release+=-DNDEBUG=1
CFLAGS=${CFLAGS-${PROFILE}}
Write a simple makefile for your args (or other) program.
Напишите простой сборочный файл для вашей программы "args" (или другой).
Libraries
mylib.h
struct TrickyStructure {
int a;
char* b;
};
void my_function(struct TrickyStructure* a);
mylib.c
#include <stdio.h>
#include "mylib.h"
static void loggg(struct TrickyStructure* a) {
printf("Hello, str is %s, num is %d\n", a->b, a->a);
}
void my_function(struct TrickyStructure* a)
{
loggg(a);
a->a += 10;
}
myuser.c:
#include <stdio.h>
#include "mylib.h"
int main() {
struct TrickyStructure ts;
ts.a = 55;
ts.b = "Hello, world";
my_function(&ts);
my_function(&ts);
printf("The number is %d\n", ts.a);
}
command line for separate object file:
$ gcc mylib.c -c -o mylib.o
$ gcc myuser.c -c -o myuser.o
$ gcc myuser.o mylib.o -o myuser
command lines for investigation
$ objdump -t myuser.o
0000000000000000 g F .text 000000000000004f main
0000000000000000 *UND* 0000000000000000 my_function
0000000000000000 *UND* 0000000000000000 printf
$ objdump -t mylib.o
0000000000000000 l F .text 0000000000000031 loggg
0000000000000000 *UND* 0000000000000000 printf
0000000000000031 g F .text 000000000000002a my_function
$ objdump -t myuser | grep 'main\|my_function\|logg'
00000000000006ff l F .text 0000000000000031 loggg
00000000000006b0 g F .text 000000000000004f main
0000000000000730 g F .text 000000000000002a my_function
Alternative tools: nm readelf
C++ may be an issue: symbol mangling.
$ gcc -x c++ mylib.c -c -o mylib.cpp.o
$ objdump -t mylib.cpp.o | grep '\<F\>'
00...0000 l F .text 00..0031 _ZL5logggP15TrickyStructure
00...0031 g F .text 00..002a _Z11my_functionP15TrickyStructure
$ echo _Z11my_functionP15TrickyStructure | c++filt my_function(TrickyStructure*)
command line for static lib:
$ ar rcs libmylib.a mylib.o
$ objdump -t libmylib.a
$ gcc myuser.o libmylib.a -o myuser
$ gcc myuser.o -L. -lmylib -o myuser
$ gcc -L. -lmylib myuser.o -o myuser # does not work
command line for dynamic lib:
$ gcc -fPIC mylib.c -c -o mylib.o
$ gcc -shared mylib.o -o libmylib.so
$ objdump -T libmylib.so
$ rm libmylib.a
$ gcc myuser.o -L. -lmylib -o myuser
$ objdump -T myuser
$ objdump -x myuser | grep NEEDED
$ readelf -Dsd myuser # alternative tool
$ readelf -a myuser | grep 'program interpreter'
$ ldd myuser
$ /lib64/ld-linux-x86-64.so.2 --library-path . ./myuser
$ LD_LIBRARY_PATH=. ./myuser
$ LD_DEBUG=libs LD_LIBRARY_PATH=. ./myuser
command line for dynamic lib with soname
$ gcc -shared -Wl,-soname,libmylib.so.0 mylib.o -o libmylib.so
$ ln -s libmylib.so libmylib.so.0
$ readelf -d libmylib.so | grep soname
$ gcc myuser.c -L. -lmylib -o myuser
$ LD_LIBRARY_PATH=. ./myuser
command line for building with rpath
$ gcc myuser.c -L. -lmylib -Wl,-rpath -Wl,'$ORIGIN' -o myuser
$ ./myuser
Write a simple C library program and C library user.
Напишите простую библиотеку на C и программы, использующую эту библиотеку.
Use LD_DEBUG=libs environment variable to see the loading works. Use readelf -s and objdump -t both on the library and on your program to see the symbols. Use ldd(1).
Используйте переменную окружения LD_DEBUG со значением "libs" чтобы увидеть, как работает загрузка библиотеки. Используйте readelf -s и objdump -t на библиотеку и программу, чтобы увидеть символы. Используйте ldd(1).
Write a makefile that builds both library and user.
Напишите Makefile, который собирает и библиотеку, и программу.
Write a library user that uses dynamic loading (dlopen(3)).
Напишите программы, динамически загружающие вашу библиотеку через dlopen(3).
Exercise 5:
implementing LD_PRELOAD
API Application Programming Interface - header file
ABI Application Binary Interface - symbols and their interpretation
Compatible ABI - can replace library file and existing program works
Compatible API - can replace library file and header, recompile program, then it works.
Version: 3.6.5
Patchlevel: 3.6.6
Minor version: 3.7.0
Major version: 4.0.0
Depends: libmycode (>= 3.6, << 4)
API change example:
Baseline: version 1.2.3
struct Qqq {
int a;
char* b;
};
void zzz(struct Qqq *a);
Minor upgrade: version 1.2.4
struct Qqq {
int a;
char* b;
};
void zzz(struct Qqq *a);
Mid upgrade: version 1.3.0
struct Qqq {
int a;
char* b;
};
void zzz(struct Qqq *a);
void yyy(long* qwerty);
Major upgrade: version 2.0.0
struct Qqq {
char* b;
int a;
float u;
};
int zzz(struct Qqq *a, int lol);
In order of priority:
$ cat ~/.config/myprogram
file_type=type5
$ cat /etc/myprogram.conf
file_type=type6
$ hd myfile.type3
00000000 46 54 59 50 32 03 15 00 00 00 00 00 00 00 00 00 |FTYP2...........|
00000010 00 00 00 4d 4b 61 40 40 71 77 65 00 00 00 00 00 |...MKa@@qwe.....|
00000020 00 00 00 00 00 00 00 00 00 |.........|
00000029
$ FILE_TYPE=type4 some_program --file-type=type1 myfile.type3