POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

POSTD PRODUCED BY NIJIBOX

POSTD PRODUCED BY NIJIBOX

ニジボックスが運営する
エンジニアに向けた
キュレーションメディア

FeedlyRSSTwitterFacebook
Chris Wellons

本記事は、原著者の許諾のもとに翻訳・掲載しております。


Linux

UNIXfork()使Linux使Linux clone() 使folk()調

   15 Pthreadx86_64 NASM  nasm-mode 




Linux

x86_64


x8664x8664使

x86_6464  16使  


rsp  

rbp  使

rax  rbx  rcx  rdx  abcd

rdi  rsi  

r8  r9  r10  r11  r12  r13  r14  r15 x86_64



r64e3216x86 16 3264x86_64


rsp  current使x86  

使x86646Linux [System V ABI](http://wiki.osdev.org/SystemV_ABI) raxfunction


rdirsirdxrcxr8r9


 foo(1,2,3) 123rdirsirdx call  mov 12 call ripcurrentrip   ret rip  rip
    mov rdi, 1
    mov rsi, 2
    mov rdx, 3
    call foo

  


rbxrsprbpr12r13r14r15


  rcxr10


rdi rsi, rdx, r10, r8, r9


Linux   rax call  syscall OSx8664vsyscall [](https://en.wikipedia.org/wiki/Return-orientedprogramming) 使 vDSO 使 syscall 

write()C
ssize_t write(int fd, const void *buf, size_t count);

x86_64では、 システムコールテーブル の上部にcall 1としてwrite()システムコールが置かれています(read()は0)。標準出力はファイルディスクリプタ1がデフォルトとして設定されています(標準入力は0)。下のコードは10バイトのデータをメモリアドレスの buffer (アセンブリ言語プログラム内の別の場所に定義済みのシンボル)から標準出力に書き出します。raxに返される値は、書き込みに成功した場合はそのバイト数、エラーの場合は-1となります。

    mov rdi, 1        ; fd
    mov rsi, buffer
    mov rdx, 10       ; 10 bytes
    mov rax, 1        ; SYS_write
    syscall

実効アドレス


1NASMNetwide [rax] C  

        2 [rax + rdx*8 + 12] raxrdxNASM [base + index*2^exp + offset] 



TLSOS使

.bssPthreads

    Linux2


brk() .bssmprotect()使

mmap() 使使brk()使


x86_64mmap()9C
void *stack_create(void);

mmap()というシステムコールでは6個の引数を使いますが、無名メモリをマッピングする際、最後の2個の引数は無視されます。私たちの目的に合わせて使用すると、C言語の以下のプロトタイプ宣言に類似します。

void *mmap(void *addr, size_t length, int prot, int flags);

flags には、スタックとなって下方へ伸びるプライベートな無名マッピングを選びます。最後のフラグであっても、システムコールはマッピングの低位アドレスを戻しますが、これが後で重要になってきます。レジスタ内に引数を設定し、システムコールを作るのは実に簡単です。

%define SYS_mmap    9
%define STACK_SIZE  (4096 * 1024)   ; 4 MB

stack_create:
    mov rdi, 0
    mov rsi, STACK_SIZE
    mov rdx, PROT_WRITE | PROT_READ
    mov r10, MAP_ANONYMOUS | MAP_PRIVATE | MAP_GROWSDOWN
    mov rax, SYS_mmap
    syscall
    ret

これで必要に応じて新しいスタック(またはスタックサイズのバッファ)を割り当てることができます。

新しいスレッドを生成する

スレッドの生成は非常に簡単で、分岐命令すら必要ありません。2個の引数を使ってclone()を呼び出し、フラグと新しいスレッドのスタックポインタをコピーします。ここで大切なのは、多くの場合、glibcのラッパー関数とシステムコールでは引数の順番が異なることです。私たちが使用しているフラグのセットでは、2個の引数を使用します。

long sys_clone(unsigned long flags, void *child_stack);

私たちのスレッドのspawn関数は、以下のようなC言語プロトタイプ宣言を持つこととなります。これは引数として関数を使い、関数を実行してスレッドを開始します。

long thread_create(void (*)(void));

関数ポインタの引数は、ABIごとにrdi経由で渡されます。stack_create()を呼び出す準備として、この引数を保管するためにスタック上に保存( push )します。これが戻ると、スタックの最低位アドレスはraxの中に格納されます。

thread_create:
    push rdi
    call stack_create
    lea rsi, [rax + STACK_SIZE - 8]
    pop qword [rsi]
    mov rdi, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | \
             CLONE_PARENT | CLONE_THREAD | CLONE_IO
    mov rax, SYS_clone
    syscall
    ret

clone()2   STACK_SIZE rax lea load effective address使rsi8


:
Thread Function Pointer = 
Higher Addresses = 
Thread Stack = 
Stack Growth = 



clone()使使clone(2)man


CLONE_THREAD 

CLONE_VM 

CLONE_PARENT 

CLONE_SIGHAND 

CLONE_FS  CLONE_FILES  CLONE_IO 


2fork()  0raxrsirsp()

 rax使 ret 使

threadcreate()IDPthread ` pthreadt ` 


 ret exit()使
%define SYS_exit    60

exit:
    mov rax, SYS_exit
    syscall

munmap()使pthread_join()wait4()使


 lock  xadd  compare-and-exchange  cmpxchg x86
監修者
監修者_古川陽介
古川陽介
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
複合機メーカー、ゲーム会社を経て、2016年に株式会社リクルートテクノロジーズ(現リクルート)入社。 現在はAPソリューショングループのマネジャーとしてアプリ基盤の改善や運用、各種開発支援ツールの開発、またテックリードとしてエンジニアチームの支援や育成までを担う。 2019年より株式会社ニジボックスを兼務し、室長としてエンジニア育成基盤の設計、技術指南も遂行。 Node.js 日本ユーザーグループの代表を務め、Node学園祭などを主宰。