情弱ログ

参考にならないので当てにしないでください

プログラミング言語が違うと実行バイナリも違うのか検証した話

こんな話が流れてきたので検証してみる.

とりあえず実行バイナリを直接吐き出す言語として,C,C++,Go,Haskellを試してみる.チョイスの理由は単に今の環境に入っていたことと,ネイティブ実行できるバイナリを吐き出す言語をこれくらいしか知らなかったため.
実行環境はGentoo Linux(amd64),C・C++コンパイラgcc 5.4.0-r3,Goコンパイラgc 1.9.1,Haskellコンパイラghc 8.0.2を用いる.

まずはコードから.
C言語

#include <stdio.h>

int main()
{
	printf("Hello, World!\n");
}

C++

#include <cstdio>

int main()
{
	printf("Hello, World!\n");
	return 0;
}

Go(wikipediaから拝借)

package main

import "fmt"

func main() {
	fmt.Print("Hello, World!\n")
}

Haskell

main = putStrLn "Hello, World!"

コンパイルしていく.

% gcc -o hello_c hello.c
% g++ -o hello_cpp hello.cpp
% go build -o hello_go hello.go
% ghc -o hello_hs hello.hs

コンパイルして出来たバイナリのファイルサイズをまず確認してみる.

% ls -lh
total 3.0M
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_c*
-rw-r--r-- 1 vicco vicco   63 Dec  2 01:01 hello.c
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_cpp*
-rw-r--r-- 1 vicco vicco   73 Dec  2 00:56 hello.cpp
-rwxr-xr-x 1 vicco vicco 1.8M Dec  2 22:21 hello_go*
-rw-r--r-- 1 vicco vicco   74 Dec  2 22:16 hello.go
-rw-r--r-- 1 vicco vicco  753 Dec  2 22:22 hello.hi
-rwxr-xr-x 1 vicco vicco 1.2M Dec  2 22:22 hello_hs*
-rw-r--r-- 1 vicco vicco   32 Dec  2 22:19 hello.hs
-rw-r--r-- 1 vicco vicco 3.5K Dec  2 22:22 hello.o

hello_cがC,hello_cppがC++,hello_goがGo,hello_hsがHaskellの出力ファイルである.CとC++のファイルサイズが7.8KBと同じであり,対してHaskellは1.2MB,Goは1.8MBと割とでかい.
次にreadelfコマンドでELFヘッダを見てみる.ELFヘッダとは実行バイナリに何が含まれているかを記述するためにファイルの先頭に置かれている64バイトのヘッダである(詳しくはman elfを読もう).大体の部分はどのファイルも同じなので,プログラムヘッダの数とセクションヘッダの数だけ見てみよう.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; readelf -h $hello|grep Number; done
hello_c
  Number of program headers:         9
  Number of section headers:         29
hello_cpp
  Number of program headers:         9
  Number of section headers:         29
hello_go
  Number of program headers:         7
  Number of section headers:         23
hello_hs
  Number of program headers:         9
  Number of section headers:         31

CとC++のプログラムヘッダおよびセクションヘッダの数は一致している.一方,GoはプログラムヘッダもセクションヘッダもC(C++)よりも少ない.Haskellはセクションヘッダの数がC(C++)より多い.これはC(C++)とGoおよびHaskellではバイナリのフォーマットが異なることを意味する.

次にlddコマンドを用いて依存している共有ライブラリを見てみる.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; ldd $hello; done
hello_c
	linux-vdso.so.1 (0x00007fff2d5f2000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f8e3b208000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8e3b5b9000)
hello_cpp
	linux-vdso.so.1 (0x00007ffee2316000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007fd4f1253000)
	libm.so.6 => /lib64/libm.so.6 (0x00007fd4f0f43000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007fd4f0d2c000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fd4f097b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd4f164c000)
hello_go
	not a dynamic executable
hello_hs
	linux-vdso.so.1 (0x00007fffe59a9000)
	libgmp.so.10 => /usr/lib64/libgmp.so.10 (0x00007f17177fb000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f17174eb000)
	librt.so.1 => /lib64/librt.so.1 (0x00007f17172e3000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f17170df000)
	libffi.so.6 => /usr/lib64/libffi.so.6 (0x00007f1716ed6000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f1716cb6000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f1716905000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f1717a73000)

CとC++はファイルサイズやELFヘッダは同じだったが,C++の方が使用する共有ライブラリの数が多い.使用する共有ライブラリの情報は.dynamicセクションに格納されており,実行時に.interpセクションに格納された動的リンカ(ローダ?)がリンクしてくれる.
7shi.hateblo.jp
qiita.com

注目すべきはGoであり,なんと一切共有ライブラリを必要としていない.つまり実行に必要なライブラリは全て静的リンクされている.実はGoはツールチェインを自前で実装しているらしく,このためlibc相当の機能(printfやscanfのような関数)も自前で賄っている.
本の虫: goのgcコンパイラーがC実装を除去

では,コンパイラの一番の仕事だと思われる,高級言語から実際にどのようなマシン語を吐き出しているのかを見ていきたい.まずは.textセクションのサイズを見ていこう.

% for hello in hello_c hello_cpp hello_go hello_hs; do echo $hello; readelf -S $hello|grep .text -A1; done # 一部編集
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
hello_c
  [12] .text             PROGBITS         00000000004003e0  000003e0
       0000000000000171  0000000000000000  AX       0     0     16
hello_cpp
  [12] .text             PROGBITS         00000000004004a0  000004a0
       0000000000000171  0000000000000000  AX       0     0     16
hello_go
  [ 1] .text             PROGBITS         0000000000401000  00001000
       0000000000086b5b  0000000000000000  AX       0     0     16
hello_hs
  [12] .text             PROGBITS         0000000000402cf0  00002cf0
       000000000008e2d9  0000000000000000  AX       0     0     16

C,C++が0x171(=369)バイトであるのに対し,Goは0x86b5b(=551771)バイト,0x8e2d9(=582361)バイトとかなり大きくなっている.
Go,Haskellは流石にでかすぎて読めないのでCとC++を逆アセンブルしていく.CとC++で先頭のアドレス部分が全て異なっていたので,awkでざっくりと取り除いてdiffコマンドで差を見ていく.

% objdump -d hello_c|awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > c.dump
% objdump -d hello_cpp|awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > cpp.dump
% diff -u c.dump cpp.dump
--- c.dump	2017-12-03 00:09:39.872448835 +0900
+++ cpp.dump	2017-12-03 00:09:45.972448843 +0900
@@ -1,33 +1,33 @@

-hello_c:     file format elf64-x86-64
+hello_cpp:     file format elf64-x86-64


 Disassembly of section .init:

-00000000004003a8 <_init>:
+0000000000400468 <_init>:
  48 83 ec 08 sub $0x8,%rsp
- 48 8b 05 45 0c 20 00 mov 0x200c45(%rip),%rax # 600ff8 <_DYNAMIC+0x1d8>
+ 48 8b 05 7d 0b 20 00 mov 0x200b7d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
  48 85 c0 test %rax,%rax
- 74 02 je 4003ba <_init+0x12>
+ 74 02 je 40047a <_init+0x12>
  ff d0 callq *%rax
  48 83 c4 08 add $0x8,%rsp
  c3 retq

 Disassembly of section .plt:

-00000000004003c0 <puts@plt-0x10>:
- ff 35 42 0c 20 00 pushq 0x200c42(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
- ff 25 44 0c 20 00 jmpq *0x200c44(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
+0000000000400480 <puts@plt-0x10>:
+ ff 35 82 0b 20 00 pushq 0x200b82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
+ ff 25 84 0b 20 00 jmpq *0x200b84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  0f 1f 40 00 nopl 0x0(%rax)

-00000000004003d0 <puts@plt>:
- ff 25 42 0c 20 00 jmpq *0x200c42(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
+0000000000400490 <puts@plt>:
+ ff 25 82 0b 20 00 jmpq *0x200b82(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  68 00 00 00 00 pushq $0x0
- e9 e0 ff ff ff jmpq 4003c0 <_init+0x18>
+ e9 e0 ff ff ff jmpq 400480 <_init+0x18>

 Disassembly of section .text:

-00000000004003e0 <_start>:
+00000000004004a0 <_start>:
  31 ed xor %ebp,%ebp
  49 89 d1 mov %rdx,%r9
  5e pop %rsi
@@ -35,10 +35,10 @@
  48 83 e4 f0 and $0xfffffffffffffff0,%rsp
  50 push %rax
  54 push %rsp
- 49 c7 c0 50 05 40 00 mov $0x400550,%r8
- 48 c7 c1 f0 04 40 00 mov $0x4004f0,%rcx
- 48 c7 c7 d6 04 40 00 mov $0x4004d6,%rdi
- ff 15 e6 0b 20 00 callq *0x200be6(%rip) # 600ff0 <_DYNAMIC+0x1d0>
+ 49 c7 c0 10 06 40 00 mov $0x400610,%r8
+ 48 c7 c1 b0 05 40 00 mov $0x4005b0,%rcx
+ 48 c7 c7 96 05 40 00 mov $0x400596,%rdi
+ ff 15 2e 0b 20 00 callq *0x200b2e(%rip) # 600ff8 <_DYNAMIC+0x208>
  f4 hlt
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
  b8 37 10 60 00 mov $0x601037,%eax
@@ -46,10 +46,10 @@
  48 2d 30 10 60 00 sub $0x601030,%rax
  48 89 e5 mov %rsp,%rbp
  48 83 f8 0e cmp $0xe,%rax
- 76 1b jbe 400440 <_start+0x60>
+ 76 1b jbe 400500 <_start+0x60>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 11 je 400440 <_start+0x60>
+ 74 11 je 400500 <_start+0x60>
  bf 30 10 60 00 mov $0x601030,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
@@ -69,10 +69,10 @@
  48 c1 e8 3f shr $0x3f,%rax
  48 01 c6 add %rax,%rsi
  48 d1 fe sar %rsi
- 74 15 je 400488 <_start+0xa8>
+ 74 15 je 400548 <_start+0xa8>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 0b je 400488 <_start+0xa8>
+ 74 0b je 400548 <_start+0xa8>
  bf 30 10 60 00 mov $0x601030,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
@@ -80,40 +80,40 @@
  5d pop %rbp
  c3 retq
  66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
- 80 3d 99 0b 20 00 00 cmpb $0x0,0x200b99(%rip) # 601030 <__TMC_END__>
- 75 11 jne 4004aa <_start+0xca>
+ 80 3d d9 0a 20 00 00 cmpb $0x0,0x200ad9(%rip) # 601030 <__TMC_END__>
+ 75 11 jne 40056a <_start+0xca>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- e8 6e ff ff ff callq 400410 <_start+0x30>
+ e8 6e ff ff ff callq 4004d0 <_start+0x30>
  5d pop %rbp
- c6 05 86 0b 20 00 01 movb $0x1,0x200b86(%rip) # 601030 <__TMC_END__>
+ c6 05 c6 0a 20 00 01 movb $0x1,0x200ac6(%rip) # 601030 <__TMC_END__>
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- bf 18 0e 60 00 mov $0x600e18,%edi
+ bf e8 0d 60 00 mov $0x600de8,%edi
  48 83 3f 00 cmpq $0x0,(%rdi)
- 75 05 jne 4004c0 <_start+0xe0>
- eb 93 jmp 400450 <_start+0x70>
+ 75 05 jne 400580 <_start+0xe0>
+ eb 93 jmp 400510 <_start+0x70>
  0f 1f 00 nopl (%rax)
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 f1 je 4004bb <_start+0xdb>
+ 74 f1 je 40057b <_start+0xdb>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
  ff d0 callq *%rax
  5d pop %rbp
- e9 7a ff ff ff jmpq 400450 <_start+0x70>
+ e9 7a ff ff ff jmpq 400510 <_start+0x70>

-00000000004004d6 <main>:
+0000000000400596 <main>:
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- bf 64 05 40 00 mov $0x400564,%edi
- e8 ec fe ff ff callq 4003d0 <puts@plt>
+ bf 24 06 40 00 mov $0x400624,%edi
+ e8 ec fe ff ff callq 400490 <puts@plt>
  b8 00 00 00 00 mov $0x0,%eax
  5d pop %rbp
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

-00000000004004f0 <__libc_csu_init>:
+00000000004005b0 <__libc_csu_init>:
  41 57 push %r15
  41 89 ff mov %edi,%r15d
  41 56 push %r14
@@ -121,15 +121,15 @@
  41 55 push %r13
  49 89 d5 mov %rdx,%r13
  41 54 push %r12
- 4c 8d 25 00 09 20 00 lea 0x200900(%rip),%r12 # 600e08 <__init_array_start>
+ 4c 8d 25 10 08 20 00 lea 0x200810(%rip),%r12 # 600dd8 <__init_array_start>
  55 push %rbp
- 48 8d 2d 00 09 20 00 lea 0x200900(%rip),%rbp # 600e10 <__init_array_end>
+ 48 8d 2d 10 08 20 00 lea 0x200810(%rip),%rbp # 600de0 <__init_array_end>
  53 push %rbx
  4c 29 e5 sub %r12,%rbp
  48 83 ec 08 sub $0x8,%rsp
- e8 8b fe ff ff callq 4003a8 <_init>
+ e8 8b fe ff ff callq 400468 <_init>
  48 c1 fd 03 sar $0x3,%rbp
- 74 1b je 40053e <__libc_csu_init+0x4e>
+ 74 1b je 4005fe <__libc_csu_init+0x4e>
  31 db xor %ebx,%ebx
  0f 1f 00 nopl (%rax)
  4c 89 ea mov %r13,%rdx
@@ -138,7 +138,7 @@
  41 ff 14 dc callq *(%r12,%rbx,8)
  48 83 c3 01 add $0x1,%rbx
  48 39 eb cmp %rbp,%rbx
- 75 ea jne 400528 <__libc_csu_init+0x38>
+ 75 ea jne 4005e8 <__libc_csu_init+0x38>
  48 83 c4 08 add $0x8,%rsp
  5b pop %rbx
  5d pop %rbp
@@ -149,12 +149,12 @@
  c3 retq
  0f 1f 00 nopl (%rax)

-0000000000400550 <__libc_csu_fini>:
+0000000000400610 <__libc_csu_fini>:
  c3 retq

 Disassembly of section .fini:

-0000000000400554 <_fini>:
+0000000000400614 <_fini>:
  48 83 ec 08 sub $0x8,%rsp
  48 83 c4 08 add $0x8,%rsp
  c3 retq

printfはコンパイラによる最適化の結果putsに置き換わっている.見比べてみると概ねjmpやcall先のアドレスが異なる程度で,後は全く同じであることが分かる.つまり,CとC++ではHello, World!程度なら同じマシン語で動くことが分かった.

以上から,同じHello, World!を出力するプログラムであっても実行バイナリは異なることが分かった,特にCやC++のような速度や最適化が売りの言語等と比較すると,GoやHaskellだとバイナリのサイズが200倍近く大きかったりする.また,実行バイナリにはマシン語だけが記述されているわけではないので,同じマシン語のプログラムであっても違う共有ライブラリに依存していたりすると実行バイナリの中身は違ってくる.今回は29個あったセクションのうち半分も比較していないので,興味がある人は他のセクションも比較してみたり,中に何が入っているのかを調べてみてほしい.バイナリの触り方を学びたい場合はBINARY HACKSがおすすめです.
Binary Hacks ―ハッカー秘伝のテクニック100選


ということで,CとC++のHello, World!は大体同じということにして終わりたいが,このままだと「printf?なんでstd::coutを使わないんだ!」って言われそうなので,ざっくりと検証してみる.以下余談なのでオチはない.
コードは以下のような感じ.

#include <iostream>

int main()
{
	std::cout << "Hello, World!" << std::endl;
	return 0;
}
% gcc -o hello2_cpp hello2.cpp
% ls -lh hello{,2}_cpp
-rwxr-xr-x 1 vicco vicco 8.4K Dec  3 00:13 hello2_cpp*
-rwxr-xr-x 1 vicco vicco 7.8K Dec  2 22:21 hello_cpp*

ファイルサイズが違う.

% for hello in hello_cpp hello2_cpp; do echo $hello; readelf -h $hello|grep Number; done
hello_cpp
  Number of program headers:         9
  Number of section headers:         29
hello2_cpp
  Number of program headers:         9
  Number of section headers:         29

ヘッダの数は同じ.

% for hello in hello_cpp hello2_cpp; do echo $hello; ldd $hello; done
hello_cpp
	linux-vdso.so.1 (0x00007ffcc98fc000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007fa14b001000)
	libm.so.6 => /lib64/libm.so.6 (0x00007fa14acf1000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007fa14aada000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fa14a729000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa14b3fa000)
hello2_cpp
	linux-vdso.so.1 (0x00007fff0d59e000)
	libstdc++.so.6 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libstdc++.so.6 (0x00007f96e4996000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f96e4686000)
	libgcc_s.so.1 => /usr/lib/gcc/x86_64-pc-linux-gnu/5.4.0/libgcc_s.so.1 (0x00007f96e446f000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f96e40be000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f96e4d8f000)

共有ライブラリは同じ(意外だった).

% for hello in hello_cpp hello2_cpp; do echo $hello; readelf -S $hello|grep .text -A1; done
hello_cpp #一部編集
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [12] .text             PROGBITS         00000000004004a0  000004a0
       0000000000000171  0000000000000000  AX       0     0     16
hello2_cpp
  [12] .text             PROGBITS         0000000000400720  00000720
       00000000000001d1  0000000000000000  AX       0     0     16

.textセクションのサイズは違う.

% objdump -d hello2_cpp |awk '$1 ~ /^[0-9a-f]+:/{$1=""; print; next}; //{print}' > cpp2.dump
% diff -u cpp.dump cpp2.dump
--- cpp.dump	2017-12-03 00:09:45.972448843 +0900
+++ cpp2.dump	2017-12-03 00:25:56.002450091 +0900
@@ -1,33 +1,58 @@

-hello_cpp:     file format elf64-x86-64
+hello2_cpp:     file format elf64-x86-64


 Disassembly of section .init:

-0000000000400468 <_init>:
+0000000000400698 <_init>:
  48 83 ec 08 sub $0x8,%rsp
- 48 8b 05 7d 0b 20 00 mov 0x200b7d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
+ 48 8b 05 4d 09 20 00 mov 0x20094d(%rip),%rax # 600ff0 <_DYNAMIC+0x200>
  48 85 c0 test %rax,%rax
- 74 02 je 40047a <_init+0x12>
+ 74 02 je 4006aa <_init+0x12>
  ff d0 callq *%rax
  48 83 c4 08 add $0x8,%rsp
  c3 retq

 Disassembly of section .plt:

-0000000000400480 <puts@plt-0x10>:
- ff 35 82 0b 20 00 pushq 0x200b82(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
- ff 25 84 0b 20 00 jmpq *0x200b84(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
+00000000004006b0 <_ZNSt8ios_base4InitC1Ev@plt-0x10>:
+ ff 35 52 09 20 00 pushq 0x200952(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
+ ff 25 54 09 20 00 jmpq *0x200954(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  0f 1f 40 00 nopl 0x0(%rax)

-0000000000400490 <puts@plt>:
- ff 25 82 0b 20 00 jmpq *0x200b82(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
+00000000004006c0 <_ZNSt8ios_base4InitC1Ev@plt>:
+ ff 25 52 09 20 00 jmpq *0x200952(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
  68 00 00 00 00 pushq $0x0
- e9 e0 ff ff ff jmpq 400480 <_init+0x18>
+ e9 e0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006d0 <__cxa_atexit@plt>:
+ ff 25 4a 09 20 00 jmpq *0x20094a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
+ 68 01 00 00 00 pushq $0x1
+ e9 d0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006e0 <_ZNSt8ios_base4InitD1Ev@plt>:
+ ff 25 42 09 20 00 jmpq *0x200942(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
+ 68 02 00 00 00 pushq $0x2
+ e9 c0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+00000000004006f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>:
+ ff 25 3a 09 20 00 jmpq *0x20093a(%rip) # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
+ 68 03 00 00 00 pushq $0x3
+ e9 b0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+0000000000400700 <_ZNSolsEPFRSoS_E@plt>:
+ ff 25 32 09 20 00 jmpq *0x200932(%rip) # 601038 <_GLOBAL_OFFSET_TABLE_+0x38>
+ 68 04 00 00 00 pushq $0x4
+ e9 a0 ff ff ff jmpq 4006b0 <_init+0x18>
+
+0000000000400710 <_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@plt>:
+ ff 25 2a 09 20 00 jmpq *0x20092a(%rip) # 601040 <_GLOBAL_OFFSET_TABLE_+0x40>
+ 68 05 00 00 00 pushq $0x5
+ e9 90 ff ff ff jmpq 4006b0 <_init+0x18>

 Disassembly of section .text:

-00000000004004a0 <_start>:
+0000000000400720 <_start>:
  31 ed xor %ebp,%ebp
  49 89 d1 mov %rdx,%r9
  5e pop %rsi
@@ -35,22 +60,22 @@
  48 83 e4 f0 and $0xfffffffffffffff0,%rsp
  50 push %rax
  54 push %rsp
- 49 c7 c0 10 06 40 00 mov $0x400610,%r8
- 48 c7 c1 b0 05 40 00 mov $0x4005b0,%rcx
- 48 c7 c7 96 05 40 00 mov $0x400596,%rdi
- ff 15 2e 0b 20 00 callq *0x200b2e(%rip) # 600ff8 <_DYNAMIC+0x208>
+ 49 c7 c0 f0 08 40 00 mov $0x4008f0,%r8
+ 48 c7 c1 90 08 40 00 mov $0x400890,%rcx
+ 48 c7 c7 16 08 40 00 mov $0x400816,%rdi
+ ff 15 ae 08 20 00 callq *0x2008ae(%rip) # 600ff8 <_DYNAMIC+0x208>
  f4 hlt
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
- b8 37 10 60 00 mov $0x601037,%eax
+ b8 5f 10 60 00 mov $0x60105f,%eax
  55 push %rbp
- 48 2d 30 10 60 00 sub $0x601030,%rax
+ 48 2d 58 10 60 00 sub $0x601058,%rax
  48 89 e5 mov %rsp,%rbp
  48 83 f8 0e cmp $0xe,%rax
- 76 1b jbe 400500 <_start+0x60>
+ 76 1b jbe 400780 <_start+0x60>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 11 je 400500 <_start+0x60>
- bf 30 10 60 00 mov $0x601030,%edi
+ 74 11 je 400780 <_start+0x60>
+ bf 58 10 60 00 mov $0x601058,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
  66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
@@ -60,60 +85,92 @@
  0f 1f 40 00 nopl 0x0(%rax)
  66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
  00 00 00
- be 30 10 60 00 mov $0x601030,%esi
+ be 58 10 60 00 mov $0x601058,%esi
  55 push %rbp
- 48 81 ee 30 10 60 00 sub $0x601030,%rsi
+ 48 81 ee 58 10 60 00 sub $0x601058,%rsi
  48 89 e5 mov %rsp,%rbp
  48 c1 fe 03 sar $0x3,%rsi
  48 89 f0 mov %rsi,%rax
  48 c1 e8 3f shr $0x3f,%rax
  48 01 c6 add %rax,%rsi
  48 d1 fe sar %rsi
- 74 15 je 400548 <_start+0xa8>
+ 74 15 je 4007c8 <_start+0xa8>
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 0b je 400548 <_start+0xa8>
- bf 30 10 60 00 mov $0x601030,%edi
+ 74 0b je 4007c8 <_start+0xa8>
+ bf 58 10 60 00 mov $0x601058,%edi
  5d pop %rbp
  ff e0 jmpq *%rax
  0f 1f 00 nopl (%rax)
  5d pop %rbp
  c3 retq
  66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
- 80 3d d9 0a 20 00 00 cmpb $0x0,0x200ad9(%rip) # 601030 <__TMC_END__>
- 75 11 jne 40056a <_start+0xca>
+ 80 3d 99 09 20 00 00 cmpb $0x0,0x200999(%rip) # 601170 <_ZSt4cout@@GLIBCXX_3.4+0x110>
+ 75 11 jne 4007ea <_start+0xca>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- e8 6e ff ff ff callq 4004d0 <_start+0x30>
+ e8 6e ff ff ff callq 400750 <_start+0x30>
  5d pop %rbp
- c6 05 c6 0a 20 00 01 movb $0x1,0x200ac6(%rip) # 601030 <__TMC_END__>
+ c6 05 86 09 20 00 01 movb $0x1,0x200986(%rip) # 601170 <_ZSt4cout@@GLIBCXX_3.4+0x110>
  c3 retq
  0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
  bf e8 0d 60 00 mov $0x600de8,%edi
  48 83 3f 00 cmpq $0x0,(%rdi)
- 75 05 jne 400580 <_start+0xe0>
- eb 93 jmp 400510 <_start+0x70>
+ 75 05 jne 400800 <_start+0xe0>
+ eb 93 jmp 400790 <_start+0x70>
  0f 1f 00 nopl (%rax)
  b8 00 00 00 00 mov $0x0,%eax
  48 85 c0 test %rax,%rax
- 74 f1 je 40057b <_start+0xdb>
+ 74 f1 je 4007fb <_start+0xdb>
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
  ff d0 callq *%rax
  5d pop %rbp
- e9 7a ff ff ff jmpq 400510 <_start+0x70>
+ e9 7a ff ff ff jmpq 400790 <_start+0x70>

-0000000000400596 <main>:
+0000000000400816 <main>:
  55 push %rbp
  48 89 e5 mov %rsp,%rbp
- bf 24 06 40 00 mov $0x400624,%edi
- e8 ec fe ff ff callq 400490 <puts@plt>
+ be 04 09 40 00 mov $0x400904,%esi
+ bf 60 10 60 00 mov $0x601060,%edi
+ e8 c7 fe ff ff callq 4006f0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
+ be 10 07 40 00 mov $0x400710,%esi
+ 48 89 c7 mov %rax,%rdi
+ e8 ca fe ff ff callq 400700 <_ZNSolsEPFRSoS_E@plt>
  b8 00 00 00 00 mov $0x0,%eax
  5d pop %rbp
  c3 retq
- 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

-00000000004005b0 <__libc_csu_init>:
+000000000040083d <_Z41__static_initialization_and_destruction_0ii>:
+ 55 push %rbp
+ 48 89 e5 mov %rsp,%rbp
+ 48 83 ec 10 sub $0x10,%rsp
+ 89 7d fc mov %edi,-0x4(%rbp)
+ 89 75 f8 mov %esi,-0x8(%rbp)
+ 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
+ 75 27 jne 400878 <_Z41__static_initialization_and_destruction_0ii+0x3b>
+ 81 7d f8 ff ff 00 00 cmpl $0xffff,-0x8(%rbp)
+ 75 1e jne 400878 <_Z41__static_initialization_and_destruction_0ii+0x3b>
+ bf 71 11 60 00 mov $0x601171,%edi
+ e8 5c fe ff ff callq 4006c0 <_ZNSt8ios_base4InitC1Ev@plt>
+ ba 50 10 60 00 mov $0x601050,%edx
+ be 71 11 60 00 mov $0x601171,%esi
+ bf e0 06 40 00 mov $0x4006e0,%edi
+ e8 58 fe ff ff callq 4006d0 <__cxa_atexit@plt>
+ 90 nop
+ c9 leaveq
+ c3 retq
+
+000000000040087b <_GLOBAL__sub_I_main>:
+ 55 push %rbp
+ 48 89 e5 mov %rsp,%rbp
+ be ff ff 00 00 mov $0xffff,%esi
+ bf 01 00 00 00 mov $0x1,%edi
+ e8 af ff ff ff callq 40083d <_Z41__static_initialization_and_destruction_0ii>
+ 5d pop %rbp
+ c3 retq
+
+0000000000400890 <__libc_csu_init>:
  41 57 push %r15
  41 89 ff mov %edi,%r15d
  41 56 push %r14
@@ -121,15 +178,15 @@
  41 55 push %r13
  49 89 d5 mov %rdx,%r13
  41 54 push %r12
- 4c 8d 25 10 08 20 00 lea 0x200810(%rip),%r12 # 600dd8 <__init_array_start>
+ 4c 8d 25 28 05 20 00 lea 0x200528(%rip),%r12 # 600dd0 <__init_array_start>
  55 push %rbp
- 48 8d 2d 10 08 20 00 lea 0x200810(%rip),%rbp # 600de0 <__init_array_end>
+ 48 8d 2d 30 05 20 00 lea 0x200530(%rip),%rbp # 600de0 <__init_array_end>
  53 push %rbx
  4c 29 e5 sub %r12,%rbp
  48 83 ec 08 sub $0x8,%rsp
- e8 8b fe ff ff callq 400468 <_init>
+ e8 db fd ff ff callq 400698 <_init>
  48 c1 fd 03 sar $0x3,%rbp
- 74 1b je 4005fe <__libc_csu_init+0x4e>
+ 74 1b je 4008de <__libc_csu_init+0x4e>
  31 db xor %ebx,%ebx
  0f 1f 00 nopl (%rax)
  4c 89 ea mov %r13,%rdx
@@ -138,7 +195,7 @@
  41 ff 14 dc callq *(%r12,%rbx,8)
  48 83 c3 01 add $0x1,%rbx
  48 39 eb cmp %rbp,%rbx
- 75 ea jne 4005e8 <__libc_csu_init+0x38>
+ 75 ea jne 4008c8 <__libc_csu_init+0x38>
  48 83 c4 08 add $0x8,%rsp
  5b pop %rbx
  5d pop %rbp
@@ -149,12 +206,12 @@
  c3 retq
  0f 1f 00 nopl (%rax)

-0000000000400610 <__libc_csu_fini>:
+00000000004008f0 <__libc_csu_fini>:
  c3 retq

 Disassembly of section .fini:

-0000000000400614 <_fini>:
+00000000004008f4 <_fini>:
  48 83 ec 08 sub $0x8,%rsp
  48 83 c4 08 add $0x8,%rsp
  c3 retq

diffを見ていくと,printfを使うhello_cppとstd::coutを使うhello2_cppではシンボル名が大きく異なることに気がつく.これはC++の名前マングリングによるものであり,例えばmain関数で呼んでいる_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_をc++filtコマンドでデマングルしてみると以下のような結果が得られる.

% c++filt
_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)

シンボル名自体に名前空間やクラス,引数の情報が含まれていることが分かる.この辺は全く知らないのでマサカリが怖い.
シンボル名の違いの他,いくつかのコードが追加されている.大きな違いは.pltセクションにいくつかの関数が追加されていること,mainで呼んでいる関数がputsではないこと,__static_initialization_and_destruction_0と_GLOBAL__sub_I_mainという関数が追加されていることだろうか.__static_initialization_and_destruction_0はstd::ios_base::Init::Init()といった初期化処理を行っているが,これを呼び出している_GLOBAL__sub_I_mainはどこから呼び出しているのかが分からなかった.何か分かったらいつかまとめよう.
stackoverflow.com

Linux Kernelのパケット送信を追う(ソケット作成編)

実績解除のためにsendto(2)のシステムコール発行からe1000ドライバの送信処理までを追いかけてみた.取り急ぎソケット作成までをまとめてみる.
なお,Linux Kernelは4.14.0-rc8を対象としている.

初めに断っておくとアホほど長い上に備忘録なのでほとんど解説はしていない.もし間違えていたらごめんなさい.

続きを読む

Hello, World!するアセンブリ

x86_64向けに動作するHello, World!を書いた.長いので続きからにする.

続きを読む

MacでLinux Kernelをcloneするといじってないファイルがmodifiedされていると言われる現象

閲覧用にMac上にLinux Kernelをcloneしたところ、いじってないファイルがmodifiedであると言われて何もできなくなる事態が発生した。
具体的には以下のようなエラーメッセージが出力される。

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   include/uapi/linux/netfilter/xt_CONNMARK.h
	modified:   include/uapi/linux/netfilter/xt_DSCP.h
	modified:   include/uapi/linux/netfilter/xt_MARK.h
	modified:   include/uapi/linux/netfilter/xt_RATEEST.h
	modified:   include/uapi/linux/netfilter/xt_TCPMSS.h
	modified:   include/uapi/linux/netfilter_ipv4/ipt_ECN.h
	modified:   include/uapi/linux/netfilter_ipv4/ipt_TTL.h
	modified:   include/uapi/linux/netfilter_ipv6/ip6t_HL.h
	modified:   net/netfilter/xt_DSCP.c
	modified:   net/netfilter/xt_HL.c
	modified:   net/netfilter/xt_RATEEST.c
	modified:   net/netfilter/xt_TCPMSS.c

検索したところ、以下のようなページが見つかった。
git - Linux Kernel sources modified on OSX right after clone - Stack Overflow

要するにHFS+ではファイル名の大文字と小文字の区別がつかないので、上のファイルは衝突してgitが混乱するというのが原因らしい。
実際に確認してみたところ、確かにinclude/uapi/linux/netfilter/xt_CONNMARK.hとinclude/uapi/linux/netfilter/xt_connmark.hというファイルが見つかった。

解決策は大文字と小文字が区別される専用のディスクイメージを作成し、その中でcloneするのが良いとのこと。ディスクユーティリティを開いて、ファイル→新規イメージ→空のイメージを作成から、フォーマットの欄で大文字/小文字を区別にすればcase-sensitiveなディスクイメージが作成できる。
結構回りくどいような…
回答者も言及しているが、Virtual Box等でVMを立ててLinux環境を作り、ssh越しに閲覧するのが早いと思う。emacs派ならTRAMPを使おう。

よく使うEmacs拡張

弊校では学部1年生からEmacsの使用が強制されており、習得度の低い学生が素のEmacsを使わされています。その結果、非常に残念なことにEmacsはただただ不便なだけのエディタとしてその名が知られています。そこで、弊校におけるEmacsの悪印象を払拭し、Emacsは便利なエディタであると布教するために僕がよく使っているEmacs拡張とその設定を挙げていきたいと思います。

続きを読む

ELF入門

C言語が書ける、アセンブラが吐き出すアセンブリコードが分かる、でもHello, worldするバイナリは何が書かれているか分からない…。というか、バイナリの実行って何?っていう疑問を解決したいバイナリ初心者のメモです。
今回はreadelfコマンドは知っていても、どんな意味がある出力なのかがいまいち分かっていなかったので、車輪の再開発で理解していこうという試みです。とりあえずreadelfに-h、-l、-Sオプションを指定した時と同じような出力ができるプログラムの作成を目指します。

続きを読む

カーネルからリンクダウン時のイベントを飛ばす処理を探す

あるインタフェースのリンク状態を確認するために、netlinkソケットを使ってカーネルからメッセージを受信するサンプルがnetlink(7)に載っています。

struct sockaddr_nl sa;

memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;

fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
bind(fd, (struct sockaddr *) &sa, sizeof(sa));

このRTMGRP_LINKなパケットがどこから飛んでくるのかを調べたのでメモ。
まず、ag(the silver searcher)でLinuxカーネル(linux-4.4.26-gentoo)をgrepすると以下の結果が得られました。

[vicco@localhost] ~
% ag RTMGRP_LINK /usr/src/linux 
/usr/src/linux/include/uapi/linux/rtnetlink.h
572:#define RTMGRP_LINK		1

/usr/src/linux/Documentation/networking/operstates.txt
25:operation RTM_GETLINK. It is also possible to subscribe to RTMGRP_LINK
136:are multicasted on the netlink group RTMGRP_LINK.
140:-subscribe to RTMGRP_LINK

(*´・ω・`).o0(使えねぇ…)

とりあえず関係しそうなコードは${KERNEL}/net/core辺りにあること、リンクダウン時はドライバで何かしらの処理がありそうだということをカーネルに精通した先輩から聞いているので、その辺りから探してみます。
まず、${KERNEL}/drivers/net/ethernet/intel/e1000e/から"down"でgrepをかけて、引っかかったnetdev.cのe1000e_down()とかいう臭そうなところから見ていきます。EmacsならM-x grep-findすればエディタ内でgrepできて便利です(ステマ)。

/**
 * e1000e_down - quiesce the device and optionally reset the hardware
 * @adapter: board private structure
 * @reset: boolean flag to reset the hardware or not
 */
void e1000e_down(struct e1000_adapter *adapter, bool reset)
{
...
	/* signal that we're down so the interrupt handler does not
	 * reschedule our watchdog timer
	 */
	set_bit(__E1000_DOWN, &adapter->state);

	netif_carrier_off(netdev);

	/* disable receives in the hardware */
	rctl = er32(RCTL);
	if (!(adapter->flags2 & FLAG2_NO_DISABLE_RX))
		ew32(RCTL, rctl & ~E1000_RCTL_EN);
	/* flush and sleep below */

	netif_stop_queue(netdev);
...

netif_carrier_off()がそれっぽい。ググったらLinux Cross Referenceとかいうサイトから${KERNEL}/net/sched/sch_generic.cにあることが分かったので、見てみると以下の様な感じになっていました。(ところでカーネル開発者の人ってタグジャンプを使ってるんでしょうか。make TAGSでタグファイルを生成できるので少なからず使ってる人はいるんでしょうが、タグファイルを開くだけで1分以上待たされますし、メモリを滅茶苦茶食う上にもっさりとジャンプするので、諦めてググってからファイルを開いてます。ただのスペ貧では)

/**
 *	netif_carrier_off - clear carrier
 *	@dev: network device
 *
 * Device has detected loss of carrier.
 */
void netif_carrier_off(struct net_device *dev)
{
	if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) {
		if (dev->reg_state == NETREG_UNINITIALIZED)
			return;
		atomic_inc(&dev->carrier_changes);
		linkwatch_fire_event(dev);
	}
}
EXPORT_SYMBOL(netif_carrier_off);

linkwatch_fire_event()、臭いっすね。${KERNEL}/net/core/link_watch.cにあるらしいので見てみます。

void linkwatch_fire_event(struct net_device *dev)
{
	bool urgent = linkwatch_urgent_event(dev);

	if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
		linkwatch_add_event(dev);
	} else if (!urgent)
		return;

	linkwatch_schedule_work(urgent);
}
EXPORT_SYMBOL(linkwatch_fire_event);

linkwatch_add_eventを見てみます。同じファイル内にあるので、Emacsならsemanticを入れておけばC-c , jでその関数にジャンプできて便利です(ステマ)。

static void linkwatch_add_event(struct net_device *dev)
{
	unsigned long flags;

	spin_lock_irqsave(&lweventlist_lock, flags);
	if (list_empty(&dev->link_watch_list)) {
		list_add_tail(&dev->link_watch_list, &lweventlist);
		dev_hold(dev);
	}
	spin_unlock_irqrestore(&lweventlist_lock, flags);
}

lweventlistというリストに突っ込んでいることが分かりました。list_add_tailとかはググったら以下のサイトに詳しく書いてありました。カーネル初心者なので基本的なところが日本語で書いてあると安心します。
list_head構造体 - Linuxの備忘録とか・・・(目次へ)

とりあえずenqueueしてそうだな、ということが分かったので、どこかでキューを処理している関数があるはず。linkwatch_fire_event()に戻って、今度はlinkwatch_schedule_work()を見てみます。

static void linkwatch_schedule_work(int urgent)
{
...
	/* Minimise down-time: drop delay for up event. */
	if (urgent) {
		if (test_and_set_bit(LW_URGENT, &linkwatch_flags))
			return;
		delay = 0;
	}
...
	/*
	 * If urgent, schedule immediate execution; otherwise, don't
	 * override the existing timer.
	 */
	if (test_bit(LW_URGENT, &linkwatch_flags))
		mod_delayed_work(system_wq, &linkwatch_work, 0);
	else
		schedule_delayed_work(&linkwatch_work, delay);
...
}

引数のurgentがtrueならlinkwatch_flagsにアトミックにLW_URGENTビットを立てて、フラグを元に下のif文で分岐しているらしい。なんで下のif文でif(urgent)って書いたら駄目なの?とかはよく分かりません。多分同時に呼ばれると困るんだと勝手に思い込んでます。
遅延処理はググったら出てきたこのサイトが詳しかったです。ちなみにこれは学部の授業の資料っぽいです。あなおそろしや。
割り込みの後半部、Softirq、Tasklet、Work Queue
上の方でstatic DECLARE_DELAYED_WORK(linkwatch_work, linkwatch_event);という定義があったので、linkwatch_eventを探すとmutexロックして__linkwatch_run_queue()といういかにもキューを処理していそうな関数を呼び出しているので見てみます。

static void __linkwatch_run_queue(int urgent_only)
{
...
	LIST_HEAD(wrk);
...
	list_splice_init(&lweventlist, &wrk);

	while (!list_empty(&wrk)) {

		dev = list_first_entry(&wrk, struct net_device, link_watch_list);
		list_del_init(&dev->link_watch_list);

		if (urgent_only && !linkwatch_urgent_event(dev)) {
			list_add_tail(&dev->link_watch_list, &lweventlist);
			continue;
		}
		spin_unlock_irq(&lweventlist_lock);
		linkwatch_do_dev(dev);
		spin_lock_irq(&lweventlist_lock);
	}
...

wrkリストという新しいリストにlweventlistを引っ付けて、wrkリストに対し処理しているようです。
list_splice_init
lweventlistを直接処理しないのはurgent_onlyな処理のためなんですかね。とりあえず中を見てみるとlinkwatch_do_dev()というのが重要そうなので見てみます。

static void linkwatch_do_dev(struct net_device *dev)
{
	/*
	 * Make sure the above read is complete since it can be
	 * rewritten as soon as we clear the bit below.
	 */
	smp_mb__before_atomic();

	/* We are about to handle this device,
	 * so new events can be accepted
	 */
	clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);

	rfc2863_policy(dev);
	if (dev->flags & IFF_UP) {
		if (netif_carrier_ok(dev))
			dev_activate(dev);
		else
			dev_deactivate(dev);

		netdev_state_change(dev);
	}
	dev_put(dev);
}

netdev_state_change()を見ていきます。${KERNEL}/net/core/dev.cにあります。

/**
 *	netdev_state_change - device changes state
 *	@dev: device to cause notification
 *
 *	Called to indicate a device has changed state. This function calls
 *	the notifier chains for netdev_chain and sends a NEWLINK message
 *	to the routing socket.
 */
void netdev_state_change(struct net_device *dev)
{
	if (dev->flags & IFF_UP) {
		struct netdev_notifier_change_info change_info;

		change_info.flags_changed = 0;
		call_netdevice_notifiers_info(NETDEV_CHANGE, dev,
					      &change_info.info);
		rtmsg_ifinfo(RTM_NEWLINK, dev, 0, GFP_KERNEL);
	}
}
EXPORT_SYMBOL(netdev_state_change);

IFF_UPはインタフェースが動作中であることを示すらしいです。この時点ではまだtrueなのかな。rtmsg_ifinfo()を追うために${KERNEL}/net/core/rtnetlink.cを見ていきます。

void rtmsg_ifinfo(int type, struct net_device *dev, unsigned int change,
		  gfp_t flags)
{
	struct sk_buff *skb;

	if (dev->reg_state != NETREG_REGISTERED)
		return;

	skb = rtmsg_ifinfo_build_skb(type, dev, change, flags);
	if (skb)
		rtmsg_ifinfo_send(skb, dev, flags);
}
EXPORT_SYMBOL(rtmsg_ifinfo);

同ファイルからrtmsg_ifinfo_send()を見てみます。

void rtmsg_ifinfo_send(struct sk_buff *skb, struct net_device *dev, gfp_t flags)
{
	struct net *net = dev_net(dev);

	rtnl_notify(skb, net, 0, RTNLGRP_LINK, NULL, flags);
}

RTNLGRP_LINKは${KERNEL}/include/uapi/linux/rtnetlink.hで以下のように定義されています。

/* RTnetlink multicast groups */
enum rtnetlink_groups {
	RTNLGRP_NONE,
#define RTNLGRP_NONE		RTNLGRP_NONE
	RTNLGRP_LINK,
#define RTNLGRP_LINK		RTNLGRP_LINK
	RTNLGRP_NOTIFY,
#define RTNLGRP_NOTIFY		RTNLGRP_NOTIFY
...
	__RTNLGRP_MAX	__RTNLGRP_MAX
};
#define RTNLGRP_MAX	(__RTNLGRP_MAX - 1)

あれ…RTMGRP_LINKは…?という疑問なんですが、同じくrtnetlink.hには#define RTMGRP_LINK 1という定義があり、RTNLGRP_LINKも1であることから、別名がついてるだけなのかな?というあやふやな感じです。別名がついているにしては、その下のRTNLGRP_NEIGHは3なのに対し、RTMGRP_NEIGHは4だったりするので正直分かってません。何なんだろう…

以上、煮え切らない感じで終わってしまいましたが、カーネルからnetlinkでRTM_NEWLINKをユーザランドに飛ばすまでの処理を見てきました。この後、ユーザランドでは以下のサイトのように、netlinkのメッセージのタイプがRTM_NEWLINKかつ、インタフェースのフラグのIFF_UPビットがtrueならup、falseならdownといった処理でインタフェースの状態を確認できます。(IFF_UPフラグ、どこでfalseにしたんだろう…)
notifications - How to get notified about network interface changes with Netlist and RTMGRP_LINK signal? - Stack Overflow