第3章 行程(process)環境
=== :::info 環境設定
Host : Distributor ID: Ubuntu Description: Ubuntu 15.10 Release: 15.10 Codename: wily
Target : i386 kernel : 4.8.1 glibc : 2.23 ::: 1.從官網抓buildroot 下來
git clone git://git.buildroot.net/buildroot
2.推到自己的github 2.1 先在自己的github帳號建立一個repository (wayling/buildroot) 2.2 改變remote url
git remote set-url origin https://github.com/wayling/buildroot.git
git remote -v (檢查一下)
git push -u origin master (上傳)
2.3去github 檢查一下 https://github.com/wayling/buildroot
3.使用buildroot
make qemu_x86_defconfig (或是使用我修改過的qemu_study_x86_defconfig)
3.1 選擇自己喜歡的選項
make menuconfig
gdb/gdbserver/strace 應該是基本款,蠻好用的,可以追glibc實際呼叫的syscall debug選項記得要開
[Toolchain]->[glibc (2.23)]
[Toolchain] -> [GCC compiler Version (gcc 4.8.x)]
[Build options]-> [strip command for binaries on target (none)]
[Build options]->[build packages with debugging symbols]
[Target packages] -> [Debugging, profiling and benchmark]->[gdb]
[Target packages] -> [Debugging, profiling and benchmark]->[gdbserver]
[Target packages] -> [Debugging, profiling and benchmark]->[full debugger]
[Target packages] -> [Debugging, profiling and benchmark]->[strace]
[Target packages] -> [System tools] -> [htop]
3.2 選擇
make linux-menuconfig
[Kernel hacking]->[Kernel debugging]
[Compile-time checks and compiler options]->[Compile the kernel with debug info]
3.3
make all
3.3.1 自己寫buildroot package 方便自己寫測試code 可以在buildroot/package/helloworld/HELLOWORLD.mk 看到範例(自己寫的) 只要在自己的PC放置自己的測試code(路徑參考HELLOWORLD_SITE,我寫的範例 https://github.com/wayling/ch3.1-start ) 執行build package就會去編譯然後產生到buildroot target
make helloworld
3.4 debug userspace 程式需要使用gdbserver
qemu-system-i386 --kernel output/images/bzImage --hda output/images/rootfs.ext2 --append "root=/dev/sda" -net nic,model=virtio -net user -redir tcp:5556:10.0.2.15:5566
3.4.1 登入qemu 裡的帳號 (root)
gdbserver 10:0.2.15:5556 ./helloworld
開啟另一個terminal 進入 buildroot/target/root
gdb ./helloworld
target remote 127.0.0.1:5566
b _start
b main
info sharelibrary (確認share library 的symbol是否載入)
From To Syms Read Shared Object Library
0xb7fdd820 0xb7ff6089 Yes (*) target:/lib/ld-linux.so.2
c
可以開始debug helloworld
Reading symbols from ./helloworld...done.
(gdb) list
1 #include <stdio.h>
2
3 int main()
4 {
5 printf("\nMain entry.\n");
6 return 0;
7 }
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0xb7fdd820 0xb7ff6089 Yes target:/lib/ld-linux.so.2
(gdb) b _start
Breakpoint 1 at 0x80482e0: file ../sysdeps/i386/start.S, line 61.
(gdb) b main
Breakpoint 2 at 0x80483ec: file main.c, line 5.
(gdb) c
Continuing.
Reading /lib/libc.so.6 from remote target...
Breakpoint 1, _start () at ../sysdeps/i386/start.S:61
61 xorl %ebp, %ebp
(gdb) list
56 .globl _start
57 .type _start,@function
58 _start:
59 /* Clear the frame pointer. The ABI suggests this be done, to mark
60 the outermost frame obviously. */
61 xorl %ebp, %ebp
62
63 /* Extract the arguments as encoded on the stack and set up
64 the arguments for `main': argc, argv. envp will be determined
65 later in __libc_start_main. */
(gdb)
3.5 debug kernel ,結合自己寫的範例
qemu-system-i386 --kernel output/images/bzImage --hda output/images/rootfs.ext2 --append "root=/dev/sda" -S -s
開啟另一個terminal 進入buildroot/output/linux-4.8.1
gdb ./vmlinux
target remote 127.0.0.1:1234
開完機,登入buildroot qemu
b do_group_exit
c
回到qemu執行helloworld
###### ./helloworld
可以再回到 gdb畫面,應該會停do_group_exit,可以開始動態追蹤了
(gdb) target remote 127.0.0.1:1234
Remote debugging using 127.0.0.1:1234
0x0000fff0 in ?? ()
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at ./arch/x86/include/asm/irqflags.h:50
50 }
(gdb) b do_group_exit
Breakpoint 1 at 0xc10486a0: file kernel/exit.c, line 931.
(gdb) c
Continuing.
Breakpoint 1, do_group_exit (exit_code=0) at kernel/exit.c:931
931 {
(gdb) list
926 * Take down every thread in the group. This is called by fatal signals
927 * as well as by sys_exit_group (below).
928 */
929 void
930 do_group_exit(int exit_code)
931 {
932 struct signal_struct *sig = current->signal;
933
934 BUG_ON(exit_code & 0x80); /* core dumps don't get here */
935
(gdb)
3.1 main是C程式的開始嗎?
Linux上程式執行有很多行為可以探討,要完整串起來不是一件簡單的事
執行檔程生
編譯 -> 組譯 -> 連結 -> 執行檔(ELF)| |-
編譯器課本
作業系統
user space -> kernel space| |- 可以期待之後的讀書會分享
- Understanding the Linux Kernel, Third Edition
Professional Linux Kernel Architecture
執行檔執行
loader -> 執行檔(ELF)| |-
1.程式設計師的自我修煉 2.可以參考jserv的很多課程 https://hackmd.io/s/rJARrHa2
⇒ main function 在user space的執行流程
何謂程式 ?
- 1.binary -> raw,ELF,PE format...
- 2.code/data segment
第1個範例
手動編譯一下
/home/wayling/kernel_study/study/buildroot/output/host/usr/bin/i686-buildroot-linux-gnu-gcc -g -v -o before.exe before.c
:::warning collect2(gcc tool) dynamic-linker /lib/ld-linux.so.2(glibc) crt1.o(glibc) -> (/sysdeps/i386/start.S) crti.o(glibc) -> /sysdeps/i386/crti.S crtbegin.o(gcc[do_global_dtors_aux]) crtend.o(gcc[do_global_ctors_aux]) crtn.o(glibc) -> (/sysdeps/i386/crtn.S ldscript -> (gcc ,output/host/usr/lib/ldscripts/elf_i386.xdw) :::
我們來檢查一下segment
objdump -l ./before.exe
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_
r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_fr
ame
03 .ctors .dtors .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag
06 .eh_frame_hdr
07
objdump -s -j .ctors ./before.exe
Contents of section .ctors:
804967c ffffffff e3830408 f7830408 0b840408 ................
804968c 00000000 ....
.ctors section 示意圖:
執行流程
_start () at ../sysdeps/i386/start.S libc_start_main at ../csu/libc-start.c libc_csu_init ../csu/libc-start.c _init ../sysdeps/i386/crti.S __do_global_ctors_aux at /root/before.exe (from gcc) _init () at ../sysdeps/i386/crtn.S
額外補充: 1.compile option有加 "-gp" 就會跑進gmon_start
2.frame_dummy是用來傳入ELF的eh_frame
在沒有 Frame Pointer 的情況下進行 Stack Unwind (scott文章)
Linux x86 Program Start Up
http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
PEFA -- debug tool http://www.ropshell.com/peda/Linux_Interactive_Exploit_Development_with_GDB_and_PEDA_Slides.pdf
3.2 "猴賽雷" exit
寫demo程式去追code 1.test_fork.exe 2.test_pthread.exe 3.test_single.exe
void
exit (int status)
{
__run_exit_handlers (status, &__exit_funcs, true);
}
/* Call all functions registered with `atexit' and `on_exit',
in the reverse of the order in which they were registered
perform stdio cleanup, and terminate program execution with STATUS. */
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
bool run_list_atexit)
{
/* First, call the TLS destructors. */
#ifndef SHARED
if (&__call_tls_dtors != NULL)
#endif
__call_tls_dtors ();
/* We do it this way to handle recursive calls to exit () made by
the functions registered with `atexit' and `on_exit'. We call
everyone on the list and use the status value in the last
exit (). */
while (*listp != NULL)
{
struct exit_function_list *cur = *listp;
while (cur->idx > 0)
{
const struct exit_function *const f =
&cur->fns[--cur->idx];
switch (f->flavor)
{
void (*atfct) (void);
void (*onfct) (int status, void *arg);
void (*cxafct) (void *arg, int status);
case ef_free:
case ef_us:
break;
case ef_on:
onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (onfct);
#endif
onfct (status, f->func.on.arg);
break;
case ef_at:
atfct = f->func.at;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (atfct);
#endif
atfct ();
break;
case ef_cxa:
cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (cxafct);
#endif
cxafct (f->func.cxa.arg, status);
break;
}
}
*listp = cur->next;
if (*listp != NULL)
/* Don't free the last element in the chain, this is the statically
allocate element. */
free (cur);
}
if (run_list_atexit)
RUN_HOOK (__libc_atexit, ());
_exit (status);
}
3.3 atexit介紹
3.3.1 使用atexit
3.3.2 atexit的侷限性
3.3.3 atexit的實現機制
int
#ifndef atexit
attribute_hidden
#endif
atexit (void (*func) (void))
{
return __cxa_atexit ((void (*) (void *)) func, NULL,
&__dso_handle == NULL ? NULL : __dso_handle);
}
int
__cxa_atexit (void (*func) (void *), void *arg, void *d)
{
return __internal_atexit (func, arg, d, &__exit_funcs);
}
int
attribute_hidden
__internal_atexit (void (*func) (void *), void *arg, void *d,
struct exit_function_list **listp)
{
struct exit_function *new = __new_exitfn (listp);
if (new == NULL)
return -1;
#ifdef PTR_MANGLE
PTR_MANGLE (func);
#endif
new->func.cxa.fn = (void (*) (void *, int)) func;
new->func.cxa.arg = arg;
new->func.cxa.dso_handle = d;
atomic_write_barrier ();
new->flavor = ef_cxa;
return 0;
}
struct exit_function *
__new_exitfn (struct exit_function_list **listp)
{
struct exit_function_list *p = NULL;
struct exit_function_list *l;
struct exit_function *r = NULL;
size_t i = 0;
__libc_lock_lock (lock);
for (l = *listp; l != NULL; p = l, l = l->next)
{
for (i = l->idx; i > 0; --i)
if (l->fns[i - 1].flavor != ef_free)
break;
if (i > 0)
break;
/* This block is completely unused. */
l->idx = 0;
}
if (l == NULL || i == sizeof (l->fns) / sizeof (l->fns[0]))
{
/* The last entry in a block is used. Use the first entry in
the previous block if it exists. Otherwise create a new one. */
if (p == NULL)
{
assert (l != NULL);
p = (struct exit_function_list *)
calloc (1, sizeof (struct exit_function_list));
if (p != NULL)
{
p->next = *listp;
*listp = p;
}
}
if (p != NULL)
{
r = &p->fns[0];
p->idx = 1;
}
}
else
{
/* There is more room in the block. */
r = &l->fns[i];
l->idx = i + 1;
}
/* Mark entry as used, but we don't know the flavor now. */
if (r != NULL)
{
r->flavor = ef_us;
++__new_exitfn_called;
}
__libc_lock_unlock (lock);
return r;
}
3.4 小心使用環境變數
int setenv(const char *name, const char *value, int overwrite);
int putenv (char *string);
int
putenv (char *string)
{
const char *const name_end = strchr (string, '=');
if (name_end != NULL)
{
char *name;
#ifdef _LIBC
int use_malloc = !__libc_use_alloca (name_end - string + 1);
if (__builtin_expect (use_malloc, 0))
{
name = strndup (string, name_end - string);
if (name == NULL)
return -1;
}
else
name = strndupa (string, name_end - string);
#else
# define use_malloc 1
name = malloc (name_end - string + 1);
if (name == NULL)
return -1;
memcpy (name, string, name_end - string);
name[name_end - string] = '\0';
#endif
int result = __add_to_environ (name, NULL, string, 1);
if (__glibc_unlikely (use_malloc))
free (name);
return result;
}
__unsetenv (string);
return 0;
}
int
setenv (const char *name, const char *value, int replace)
{
if (name == NULL || *name == '\0' || strchr (name, '=') != NULL)
{
__set_errno (EINVAL);
return -1;
}
return __add_to_environ (name, value, NULL, replace);
}
/* This function is used by `setenv' and `putenv'. The difference between
the two functions is that for the former must create a new string which
is then placed in the environment, while the argument of `putenv'
must be used directly. This is all complicated by the fact that we try
to reuse values once generated for a `setenv' call since we can never
free the strings. */
int
__add_to_environ (const char *name, const char *value, const char *combined,
int replace)
{
char **ep;
size_t size;
/* Compute lengths before locking, so that the critical section is
less of a performance bottleneck. VALLEN is needed only if
COMBINED is null (unfortunately GCC is not smart enough to deduce
this; see the #pragma at the start of this file). Testing
COMBINED instead of VALUE causes setenv (..., NULL, ...) to dump
core now instead of corrupting memory later. */
const size_t namelen = strlen (name);
size_t vallen;
if (combined == NULL)
vallen = strlen (value) + 1;
LOCK;
/* We have to get the pointer now that we have the lock and not earlier
since another thread might have created a new environment. */
ep = __environ;
size = 0;
if (ep != NULL)
{
for (; *ep != NULL; ++ep)
if (!strncmp (*ep, name, namelen) && (*ep)[namelen] == '=')
break;
else
++size;
}
if (ep == NULL || __builtin_expect (*ep == NULL, 1))
{
char **new_environ;
/* We allocated this space; we can extend it. */
new_environ = (char **) realloc (last_environ,
(size + 2) * sizeof (char *));
if (new_environ == NULL)
{
UNLOCK;
return -1;
}
if (__environ != last_environ)
memcpy ((char *) new_environ, (char *) __environ,
size * sizeof (char *));
new_environ[size] = NULL;
new_environ[size + 1] = NULL;
ep = new_environ + size;
last_environ = __environ = new_environ;
}
if (*ep == NULL || replace)
{ //putenv
char *np;
/* Use the user string if given. */
if (combined != NULL)
np = (char *) combined;
else
{
const size_t varlen = namelen + 1 + vallen;
#ifdef USE_TSEARCH
char *new_value;
int use_alloca = __libc_use_alloca (varlen);
if (__builtin_expect (use_alloca, 1))
new_value = (char *) alloca (varlen);
else
{
new_value = malloc (varlen);
if (new_value == NULL)
{
UNLOCK;
return -1;
}
}
# ifdef _LIBC
__mempcpy (__mempcpy (__mempcpy (new_value, name, namelen), "=", 1),
value, vallen);
# else
memcpy (new_value, name, namelen);
new_value[namelen] = '=';
memcpy (&new_value[namelen + 1], value, vallen);
# endif
np = KNOWN_VALUE (new_value);
if (__glibc_likely (np == NULL))
#endif
{
#ifdef USE_TSEARCH
if (__glibc_unlikely (! use_alloca))
np = new_value;
else
#endif
{
np = malloc (varlen);
if (__glibc_unlikely (np == NULL))
{
UNLOCK;
return -1;
}
#ifdef USE_TSEARCH
memcpy (np, new_value, varlen);
#else
memcpy (np, name, namelen);
np[namelen] = '=';
memcpy (&np[namelen + 1], value, vallen);
#endif
}
/* And remember the value. */
STORE_VALUE (np);
}
#ifdef USE_TSEARCH
else
{
if (__glibc_unlikely (! use_alloca))
free (new_value);
}
#endif
}
*ep = np;
}
UNLOCK;
return 0;
}
3.5 使用動態函式庫
$(CC) -Wall -g -shared -fPIC dlib.c -o libdlib.so
$(CC) -Wall -g main.c -o example -L./ -ldlib
$(CC) -Wall -g main_1.c -o example_dl -L./ -ldlib -ldl
3.5.1 動態與靜態函式庫
編譯選項 : -shared -fPIC
3.5.2 編譯與使用動態函式庫
3.5.3 程式的平滑無縫升級
手動載入share object
3.6 避免記憶體問題
3.6.1 尷尬的realloc
3.6.2 如何防止記憶體越界
- char strncat(char dest,const char *src, size_t n);
- char strncpy(char dest, const char *src, size_t n);
- int snprintf(char str, size_t size, const char format, ...);
- char fgets(char s, int size, FILE *stream);
3.6.3 如何定位記憶體問題
記憶體問題檢測工具 - valgrind http://valgrind.org/
3.7 "長轉跳" longjmp
3.7.1 setjmp與longjmp的使用
3.7.2 "長轉跳"的實現機制
glibc/sysdeps/i386
setjmp -> (setjmp.S) longjmp -> (__longjmp.S)
3.7.3 "長轉跳"的限制
Bug 1: Bug 2:
setjmp/longjmp 應用 1.coroutine 2.c語言的exception handler