gccのインラインアセンブラ内からcのラベルにgotoでジャンプ

最近のgccインラインアセンブラ内からcのgotoラベルに飛べる仕組みがあるのでそれのメモです。

ドキュメントはUsing the GNU Compiler Collection (GCC): Extended AsmUsing the GNU Compiler Collection (GCC): Extended Asmです。たしかLKMLに送られてたpatchにもこれを使ったコードがあった気がします。

それはさておき、gotoを使う場合の書式は以下のようになっていて、asm gotoという形式です。

asm [volatile] goto ( AssemblerTemplate 
                      : 
                      : InputOperands
                      : Clobbers
                      : GotoLabels)

ドキュメントには-ansiオプションを付けてコンパイルする場合はasmではなくてasmを使うようにと書かれています。また、出力のオペランドは指定できないようです。そのため、計算の途中経過を保持したいときとかはClobbersのところでメモリに書き出すようにするみたいです。

以下が簡単なサンプルです。argcの数が3かどうか調べて3だったらsameラベルにジャンプしてます。

#include <stdio.h>

int main(int argc, char **argv)
{
    __asm__ goto("cmpl $0x3, %[argc]\n\t"
             "je %l[same]\n\t"
             :
             : [argc] "r"(argc)
             :
             :same);

    printf("argc is %d\n", argc);

    return 0;
same:
    printf("argc == 3\n");
    return 0;
}

機械語になるとこんな感じです。

0000000000400527 <main>:                                                                                                                                                                                           
  400527:       55                      push   %rbp                                                                                                                                                                
  400528:       48 89 e5                mov    %rsp,%rbp                                                                                                                                                           
  40052b:       48 83 ec 10             sub    $0x10,%rsp                                                                                                                                                          
  40052f:       89 7d fc                mov    %edi,-0x4(%rbp)                                                                                                                                                     
  400532:       48 89 75 f0             mov    %rsi,-0x10(%rbp)                                                                                                                                                    
  400536:       8b 45 fc                mov    -0x4(%rbp),%eax                                                                                                                                                     
  400539:       83 f8 03                cmp    $0x3,%eax                                                                                                                                                           
  40053c:       74 1b                   je     400559 <main+0x32>                                                                                                                                                  
  40053e:       8b 45 fc                mov    -0x4(%rbp),%eax                                                                                                                                                     
  400541:       89 c6                   mov    %eax,%esi                                                                                                                                                           
  400543:       bf 00 06 40 00          mov    $0x400600,%edi                                                                                                                                                      
  400548:       b8 00 00 00 00          mov    $0x0,%eax                                                                                                                                                           
  40054d:       e8 ee fe ff ff          callq  400440 <printf@plt>                                                                                                                                                 
  400552:       b8 00 00 00 00          mov    $0x0,%eax                                                                                                                                                           
  400557:       eb 0f                   jmp    400568 <main+0x41>                                                                                                                                                  
  400559:       bf 0c 06 40 00          mov    $0x40060c,%edi                                                                                                                                                      
  40055e:       e8 cd fe ff ff          callq  400430 <puts@plt>                                                                                                                                                   
  400563:       b8 00 00 00 00          mov    $0x0,%eax
  400568:       c9                      leaveq
  400569:       c3                      retq
  40056a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

低レベルプログラミング

低レベルプログラミング

vdsoの領域を読んでファイルに保存

ふとvdsoで提供されている関数の一覧が見たくなったので。

dump vdso

masami@saga:~$ gcc read_vdso.c -o read_vdso                                                                                                                                                                        
masami@saga:~$ ./read_vdso                                                                                                                                                                                         
[*]read 2 pages                                     
[*]vdso start address is 0x7ffd532fb000             
[*]write data                                       
[*]Done.                                            
masami@saga:~$ file vdso_dump.so                                                                                                                                                                                   
vdso_dump.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=dbcfbfba9068a599aa0b0964ac2ab58244ce7cb4, stripped                                                         
masami@saga:~$ readelf -s vdso_dump.so                                                                                                                                                                             

Symbol table '.dynsym' contains 10 entries:         
   Num:    Value          Size Type    Bind   Vis      Ndx Name                                          
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND                                               
     1: 0000000000000a20   773 FUNC    WEAK   DEFAULT   12 clock_gettime@@LINUX_2.6                      
     2: 0000000000000d30   449 FUNC    GLOBAL DEFAULT   12 __vdso_gettimeofday@@LINUX_2.6                
     3: 0000000000000d30   449 FUNC    WEAK   DEFAULT   12 gettimeofday@@LINUX_2.6                       
     4: 0000000000000f00    21 FUNC    GLOBAL DEFAULT   12 __vdso_time@@LINUX_2.6                        
     5: 0000000000000f00    21 FUNC    WEAK   DEFAULT   12 time@@LINUX_2.6                               
     6: 0000000000000a20   773 FUNC    GLOBAL DEFAULT   12 __vdso_clock_gettime@@LINUX_2.6               
     7: 0000000000000000     0 OBJECT  GLOBAL DEFAULT  ABS LINUX_2.6                                     
     8: 0000000000000f20    42 FUNC    GLOBAL DEFAULT   12 __vdso_getcpu@@LINUX_2.6                      
     9: 0000000000000f20    42 FUNC    WEAK   DEFAULT   12 getcpu@@LINUX_2.6   

vdsoの開始アドレスはgetauxval(3)にAT_SYSINFO_EHDRを渡すと取得できる。

xv6のメモリ管理周りのコードリーディング

xv6のページング周りをちょっと見てたので記録をφ(..)メモメモ

参照したドキュメントはbook-rev10.pdfです。

book-rev10.pdfのP21、 Figure 1-2にxv6のメモリレイアウトがあります。

f:id:masami256:20180305233136p:plain

仮想アドレスの0から0x80000000がユーザー空間で、0x80000000〜0xFFFFFFFFがカーネル空間ですね。カーネル空間のうち先頭の0x100000バイトはBIOSの領域となってます。

カーネルアドレス空間vm.cにあるkmap構造体で管理してます。

static struct kmap {
  void *virt;
  uint phys_start;
  uint phys_end;
  int perm;
} kmap[] = {
 { (void*)KERNBASE, 0,             EXTMEM,    PTE_W}, // I/O space
 { (void*)KERNLINK, V2P(KERNLINK), V2P(data), 0},     // kern text+rodata
 { (void*)data,     V2P(data),     PHYSTOP,   PTE_W}, // kern data+memory
 { (void*)DEVSPACE, DEVSPACE,      0,         PTE_W}, // more devices
};

KERNBASEやKERNLINKなどの定数はmemorylayout.hにて定義されています。

#define EXTMEM  0x100000            // Start of extended memory
#define PHYSTOP 0xE000000           // Top physical memory
#define DEVSPACE 0xFE000000         // Other devices are at high addresses

// Key addresses for address space layout (see kmap in vm.c for layout)
#define KERNBASE 0x80000000         // First kernel virtual address
#define KERNLINK (KERNBASE+EXTMEM)  // Address where kernel is linked

V2Pはこんなマクロです。

#define V2P(a) (((uint) (a)) - KERNBASE)

ここでkmapのコメントにあるようなメモリレイアウトにしてるわけですね。

//   0..KERNBASE: user memory (text+data+stack+heap), mapped to
//                phys memory allocated by the kernel
//   KERNBASE..KERNBASE+EXTMEM: mapped to 0..EXTMEM (for I/O space)
//   KERNBASE+EXTMEM..data: mapped to EXTMEM..V2P(data)
//                for the kernel's instructions and r/o data
//   data..KERNBASE+PHYSTOP: mapped to V2P(data)..PHYSTOP,
//                                  rw data + free physical memory
//   0xfe000000..0: mapped direct (devices such as ioapic)

このkmap構造体を使ってマッピングを行っているのがsetupkvm()です。setupkvm()の戻り値の型はpde_t *で、ようはページグローバルディレクトリです。これのアドレスがcr3レジスタに設定されます。

setupkvm()を呼ぶのは4箇所有ります。

exec()ではexecシステムコールで既存のプロセスのアドレス空間を新しいプロセスのアドレス空間に変える時に呼んでいます。userinit()は一番最初のユーザープロセスを作る時に呼んでいます。 kvmalloc()ではカーネルのスケジューラー用に作っているようです。最後のcopyuvm()はfork()の実行時に親プロセスのページテーブルをコピーする時に呼んでいます。

実際にマッピングを行っているのはmappages()です。mappages()は引数としてページグローバルディレクトリの先頭アドレス、マッピングしたい仮想アドレス、そのアドレスのサイズ、仮想アドレスにマッピングする物理アドレス、ページに設定するパーミッションを受け取ります。1番目のページグローバルディレクトリは呼び出し側がkalloc()でメモリを確保しています。kalloc()はkalloc.cに実装があって、空きページを1ページ確保して返す関数です。

mappages()を呼び出している箇所は4箇所です。

setupkvm()は先程も出てきたとおりで、kmap構造体のデータを実際にマッピングしてます。inituvm()はuserinit()の処理中に呼ばれます。_binary_initcode_startなどはカーネルをビルドした後にできるkernel.symというファイルで確認できます。allocuvm()はユーザー空間の大きさを大きくする時に呼んでいます。copyuvm()はfork()の実行時に呼ばれます。

ページフォルトですがxv6ではデマンドページングはやっていないのでエラーとして処理してます。処理しているのはtrap.cにあるtrap()です。カーネルアドレス空間ページフォルトが発生した場合はpanic、ユーザー空間の場合はプロセスを終了させるようです。

default:
    if(myproc() == 0 || (tf->cs&3) == 0){
      // In kernel, it must be our mistake.
      cprintf("unexpected trap %d from cpu %d eip %x (cr2=0x%x)\n",
              tf->trapno, cpuid(), tf->eip, rcr2());
      panic("trap");
    }
    // In user space, assume process misbehaved.
    cprintf("pid %d %s: trap %d err %d on cpu %d "
            "eip 0x%x addr 0x%x--kill proc\n",
            myproc()->pid, myproc()->name, tf->trapno,
            tf->err, cpuid(), tf->eip, rcr2());
    myproc()->killed = 1;

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

はじめてのOSコードリーディング ~UNIX V6で学ぶカーネルのしくみ (Software Design plus)

ページングでメモリを割り当てる処理の動きを確認できるものを作ってみた

Linuxのしくみの5章でページングの説明がありますね。なんとなく、この辺の挙動をエミュレートする感じのものでも作ってみようかなと思ったりしたので。

リポジトリはこちらです。

github.com

ページングのコードを実際に書くなら自作OSを書くのが良いと思いますが、ページの割り当てくらいならユーザーランドのプログラムでもそれっぽい感じにはなるかなということで^^;

動かし方はこんな感じで、4MiBのメモリがあって、4プロセスが5回ずつページの割り当てを行った感じが下のコマンドです。

masami@saga:~/codes/how_paging_works (master=)$ ./pg -p 4 -l 5 -m 4 | jq .

出力はjson形式になるようにしたので、pipeでjqコマンドに渡せば綺麗に見れます。

{
  "physical memory": {
    "size": "4MiB",
    "range": {
      "start": "0x7fd86ae2e000",
      "end": "0x7fd86b22e000"
    },
    "page flames": 1024
  },
  "page data": [
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2e000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae2f000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae30000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae31000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae32000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae33000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae34000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae35000"
    },
    {
      "pid": 2,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae36000"
    },
    {
      "pid": 1,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae37000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae38000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae39000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3a000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae3b000"
    },
    {
      "pid": 3,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae3c000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 0,
      "vaddr": "0x100000000000",
      "paddr": "0x7fd86ae3d000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 1,
      "vaddr": "0x100000001000",
      "paddr": "0x7fd86ae3e000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 2,
      "vaddr": "0x100000002000",
      "paddr": "0x7fd86ae3f000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 3,
      "vaddr": "0x100000003000",
      "paddr": "0x7fd86ae40000"
    },
    {
      "pid": 4,
      "pgd_idx": 32,
      "pud_idx": 0,
      "pmd_idx": 0,
      "pte_idx": 4,
      "vaddr": "0x100000004000",
      "paddr": "0x7fd86ae41000"
    }
  ]
}

プロセスのアドレス空間(仮想アドレス)は0x100000000000からスタートしてます。物理アドレスの方はposix_memalign(3)で取得したアドレスで、ASLRが有効な環境というのもあってどんな値かは基本的に予測できません。その代わりページサイズでアライメントを調整してあります。

このプログラムがやってるのは、こんな感じです。

  1. 最初に指定されたサイズのメモリを確保
  2. ページフレームを管理する構造体の初期化
  3. -pオプションで指定された数のスレッドを準備(プロセスの代わりにスレッドを使ってます)
  4. スレッドは-lオプションで指定された回数のループでページの割り当てを行う
  5. 全部のスレッドが終了したらjson形式のデータを出力

ページグローバルディレクトリのpgdはスレッドの開始時にそのスレッド用に作ってます。pmd、pud、pteはdo_page_fault()の中で作ってます。なんでdo_page_fault()って名前かって言うと、ページフォルトが起きた時に呼ばれる関数がこれなので(ここから更に関数の呼び出しはありますが)、それっぽい名前にしました。 他にもpgdやpte用のメモリ確保はpgd_alloc()や、pte_alloc()でLinuxを同じような名前にしてます。

これ自体は単にメモリを確保していって最後に表示するだけですが、適当に弄るともうちょっと遊べるんじゃないかと思いますm( )m

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識 読みました

著者の武内さんより「[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識」を献本いただきました。ありがとうございますm( )m

f:id:masami256:20180218160317j:plain:w250

武内さんはQiitaなどでもわかりやすいカーネルの解説記事を書かれているので、記事を読んだことが有る方も多いかと思います。そして、今回「試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識」というOS・ハードウェアのしくみを手を動かして学ぶコンセプトの本を書かれました。

gihyo.jp

本書の「実験と図解で学ぶOSとハードウェアの基礎知識」というコンセプトはOSの解説本にしては珍しいと思います。しかし、何かを学ぶ上で実際に手を動かしたほうが勉強になるということを考えるとこのコンセプトは良いですよね。 また、図解の部分も丁寧に解説されています。第5章の仮想記憶、ページングの図解などはここまで細く説明されている文献はそうそうないと思います。もちろん5章だけでなく他の章も図が豊富に使われていて理解がはかどります。

章立ては1〜8章まであって、1章はコンピュータシステムの概要説明となり、以降の章に入る前の基礎知識を説明しています。2〜7章は基本的には他の章への依存は少ないので興味のあるところから読むというのも良いと思います。例えば、2章のプロセス生成では仮想記憶に関する知識も合ったほうがよく、仮想記憶の詳細は5章で解説しますという形になっています。 しかし、2章では仮想記憶の詳細がわからないくて理解できるようにプロセス管理についてを説明されているので、本を読み進めるために必要な前提知識といったハードルは低くなっています。

また、解説は本質的な内容の理解というところに注力していると思います。たとえば、Linuxx86_64ならユーザモードとカーネルモードの2種類のモードがあって、ユーザモードは実行可能な命令が制限されるというのがありますよね。これもcpuの機能だとx86Ring Protection機能が出てきたししますが、そういった詳細な部分には踏み込まずにモード分けることの意味について説明されているといった感じです。細かい内容はともかく一番大事なところの知識を得ることができます。詳細を知りたくなったら、あとがきに書かれている書籍にチャレンジしましょう!

自分はLinuxカーネルもくもく会というネタをLinuxカーネルに絞ったもくもく会を開催しているのですが、そうするとカーネルに興味があるけどどこから手を付ければ良いのか???というような悩みを聞くことがよくあります。カーネルに興味があって、その中でも明確にXXXという機能に興味があるという場合は良いですが、そうでない場合も結構ありますよね。そういう人には本当にオススメできる本だと思います。OSカーネルの解説本はLinuxに限らず色々出版されていてどれも良い内容なんですが、最初にどれを読めばいいか?と聞かれると意外と困ったりします。本を読むための前提知識といったハードルは極力低くしつつ、丁寧に解説がされていて、なおかつ手を動かして実際に試せるというところで本書のようなコンセプトの本は入門に最適です。

本書ではLinuxカーネルのコードの説明はしていなくて、普通のアプリケーションプログラムを使って/作ってLinuxのしくみを実験するという方針を取られています。そのため、Linuxカーネルにも手を出したいという場合は、本書とお好きなLinuxカーネルの解説本やカーネルソースコードを用意して本書を読みながらカーネルの実装を調べるという進め方になっていくと思います。といっても、カーネルに慣れていない場合はこのような読み方はしないで、まずは本書をじっくり読み進め、次に本書とカーネルのソースを読み進めるのがオススメな流れです。

OSやハードウェアについて知りたい方は読んで実験してみましょう!

Linux kernelをgdbでリモートデバッグするときはKASLRをoffにするのを忘れずに(´・ω・`)

カーネルgdbでリモートデバッグできるようにするかーとか思ってやってたわけです。

しかし、下記のような感じでちょっとハマってました。

  • ブレークポイントが効かなかったり
  • vmlinuxとvmlinuzの組が合ってるのにシンボル名が出なかったり
  • Cannot access memory at address 0xffffffff8124d730とかで落ちたり

f:id:masami256:20180215221450p:plain

原因はKASLRが有効だったため、実行時にアドレスがランダマイズされていたのが原因ですね\(^o^)/

解決策はKASLRをoffにすれば良いだけです。そのためにカーネルの再ビルドは不要で、カーネルコマンドラインでnokaslrを渡せばOKです。

そうすればこのようにいい感じに f:id:masami256:20180215221904p:plain

以下はめも

remote接続にシリアルコンソールを使う場合

qemuはこんな感じになる。

$ qemu-system-x86_64 -kernel vmlinuz -initrd initramfs.img -m 2048 -append "nokaslr console=tty0 kgdboc=ttyS0,115200 kgdbwait" -chardev pty,id=pty -device isa-serial,chardev=pty 

そうすると以下のようなメッセージが出るので、gdbではこのデバイスに対してアクセスする。

qemu-system-x86_64: -chardev pty,id=pty: char device redirected to /dev/pts/2 (label pty)                

gdbからリモートアクセスするとこんな感じに。

gdb-peda$ target remote /dev/pts/2                  
Remote debugging using /dev/pts/2                   
Warning: not running or target is remote            
kgdb_breakpoint () at kernel/debug/debug_core.c:1073                                                     
1073            wmb(); /* Sync point after breakpoint */  

remote接続にTCPを使う場合

この場合は普通に-sオプションを使えばOK。

$ qemu-system-x86_64 -kernel vmlinuz -initrd initramfs.img -m 2048 -append "nokaslr" -s -S 

gdbからアクセスするときもこのように。

masami@saga:~/tmp/test$ gdb ./vmlinux               
Reading symbols from ./vmlinux...done.              
gdb-peda$ dir usr/src/debug/kernel-4.15.fc27/linux-4.15.2-300.fc27.x86_64/                               
Source directories searched: /home/masami/tmp/test/usr/src/debug/kernel-4.15.fc27/linux-4.15.2-300.fc27.x86_64:$cdir:$cwd                                                                                          
gdb-peda$ target remote :1234                       
Remote debugging using :1234                        
Warning: not running or target is remote            
0x000000000000fff0 in cpu_hw_events ()              
gdb-peda$ b __kmalloc                               
Breakpoint 1 at 0xffffffff8124d730: file mm/slub.c, line 3746.                                           
gdb-peda$ c                                         
Continuing.                                         
Warning: not running or target is remote            

Breakpoint 1, __kmalloc (size=size@entry=0xa8, flags=flags@entry=0x14080c0) at mm/slub.c:3746            
3746    {                                           
gdb-peda$                  

低レベルプログラミング

低レベルプログラミング

Linuxの/proc/cpuinfoでflagsを表示しているところの仕組みめも

/proc/cpuinfoでcpuの機能を表示するflagsのところってどうやって名称設定してんだろ?と思ったので調べてみたメモです。調べたカーネルのバージョンはv4.14.12です。

↓これですね f:id:masami256:20180110231210p:plain

/proc/cpuinfoのopen処理

これはfs/proc/cpuinfo.cにあるcpuinfo_open()が担当します。と言っても、実際の処理はseq_operations構造体のcpuinfo_opに関数がセットされていて、それらが実行されます。

extern const struct seq_operations cpuinfo_op;
static int cpuinfo_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &cpuinfo_op);
}

ここからはアーキテクチャ固有になります。我らがx86_64ではarch/x86/kernel/cpu/proc.cにて以下のように設定を行っています。

onst struct seq_operations cpuinfo_op = {
    .start  = c_start,
    .next   = c_next,
    .stop   = c_stop,
    .show   = show_cpuinfo,
};

名前からしてshow_cpuinfo()を見れば良さそうに思えるのでこれを見てみます。

show_cpuinfo()

show_cpuinfo()が実際にcpuの情報を表示する関数です。

上から見ていくとこのようなところがみつかります。ここがflagsを出しているとこですね。

 seq_puts(m, "flags\t\t:");
    for (i = 0; i < 32*NCAPINTS; i++)
        if (cpu_has(c, i) && x86_cap_flags[i] != NULL)
            seq_printf(m, " %s", x86_cap_flags[i]);

seq_printf()はフォーマット指定子に%sを使っていて、引数はx86_cap_flags[i]なので、配列のx86_cap_flagsに名称が入ってるのがわかります。 そしたらx86_cap_flagsがどこで設定されてるか調べればOKですね。

x86_cap_flags配列

この配列ですが使ってるファイルは3ファイルです。

f:id:masami256:20180110232436p:plain

で、実際にこの配列に値を設定している箇所はmakeの実行前には存在しません(´・ω・`) なので、elixir.free-electrons.comだと見れないんですね。

x86_cap_flags配列の設定

ここで手元のカーネルになるので4.15.0-rc7になるんですが、arch/x86/kernel/cpu/capflags.cが設定している感じですね。

masami@saga:~/linux-kernel (test %)$ find arch/x86 -name "*.c" -o -name "*.h" | xargs grep -n "x86_cap_flags"
arch/x86/kernel/cpu/proc.c:102:         if (cpu_has(c, i) && x86_cap_flags[i] != NULL)
arch/x86/kernel/cpu/proc.c:103:                 seq_printf(m, " %s", x86_cap_flags[i]);
arch/x86/kernel/cpu/capflags.c:5:const char * const x86_cap_flags[NCAPINTS*32] = {
arch/x86/boot/mkcpustr.c:32:                    str = x86_cap_flags[i*32+j];
arch/x86/include/asm/cpufeature.h:35:extern const char * const x86_cap_flags[NCAPINTS*32];
arch/x86/include/asm/cpufeature.h:38:#define x86_cap_flag(flag) x86_cap_flags[flag]

このファイルはこんな内容です。

#ifndef _ASM_X86_CPUFEATURES_H
#include <asm/cpufeatures.h>
#endif

const char * const x86_cap_flags[NCAPINTS*32] = {
    [X86_FEATURE_FPU]        = "fpu",
    [X86_FEATURE_VME]        = "vme",
    [X86_FEATURE_DE]         = "de",
    [X86_FEATURE_PSE]        = "pse",
    [X86_FEATURE_TSC]        = "tsc",
    [X86_FEATURE_MSR]        = "msr",
    [X86_FEATURE_PAE]        = "pae",
    [X86_FEATURE_MCE]        = "mce",
    [X86_FEATURE_CX8]        = "cx8",
    [X86_FEATURE_APIC]       = "apic",
    [X86_FEATURE_SEP]        = "sep",
    [X86_FEATURE_MTRR]       = "mtrr",
    [X86_FEATURE_PGE]        = "pge",
    [X86_FEATURE_MCA]        = "mca",
    [X86_FEATURE_CMOV]       = "cmov",
    [X86_FEATURE_PAT]        = "pat",
    [X86_FEATURE_PSE36]      = "pse36",
    [X86_FEATURE_PN]         = "pn",
    [X86_FEATURE_CLFLUSH]        = "clflush",
    [X86_FEATURE_DS]         = "dts",
    [X86_FEATURE_ACPI]       = "acpi",
    [X86_FEATURE_MMX]        = "mmx",
    [X86_FEATURE_FXSR]       = "fxsr",
    [X86_FEATURE_XMM]        = "sse",
    [X86_FEATURE_XMM2]       = "sse2",
    [X86_FEATURE_SELFSNOOP]      = "ss",
    [X86_FEATURE_HT]         = "ht",
    [X86_FEATURE_ACC]        = "tm",
    [X86_FEATURE_IA64]       = "ia64",
    [X86_FEATURE_PBE]        = "pbe",
    [X86_FEATURE_SYSCALL]        = "syscall",
    [X86_FEATURE_MP]         = "mp",
    [X86_FEATURE_NX]         = "nx",
    [X86_FEATURE_MMXEXT]         = "mmxext",
    [X86_FEATURE_FXSR_OPT]       = "fxsr_opt",
    [X86_FEATURE_GBPAGES]        = "pdpe1gb",
    [X86_FEATURE_RDTSCP]         = "rdtscp",
    [X86_FEATURE_LM]         = "lm",
    [X86_FEATURE_3DNOWEXT]       = "3dnowext",
    [X86_FEATURE_3DNOW]      = "3dnow",
    [X86_FEATURE_RECOVERY]       = "recovery",
    [X86_FEATURE_LONGRUN]        = "longrun",
    [X86_FEATURE_LRTI]       = "lrti",
    [X86_FEATURE_CXMMX]      = "cxmmx",
    [X86_FEATURE_K6_MTRR]        = "k6_mtrr",
    [X86_FEATURE_CYRIX_ARR]      = "cyrix_arr",
    [X86_FEATURE_CENTAUR_MCR]    = "centaur_mcr",
    [X86_FEATURE_CONSTANT_TSC]   = "constant_tsc",
    [X86_FEATURE_UP]         = "up",
    [X86_FEATURE_ART]        = "art",
    [X86_FEATURE_ARCH_PERFMON]   = "arch_perfmon",
    [X86_FEATURE_PEBS]       = "pebs",
    [X86_FEATURE_BTS]        = "bts",
    [X86_FEATURE_REP_GOOD]       = "rep_good",
    [X86_FEATURE_ACC_POWER]      = "acc_power",
    [X86_FEATURE_NOPL]       = "nopl",
    [X86_FEATURE_XTOPOLOGY]      = "xtopology",
    [X86_FEATURE_TSC_RELIABLE]   = "tsc_reliable",
    [X86_FEATURE_NONSTOP_TSC]    = "nonstop_tsc",
    [X86_FEATURE_CPUID]      = "cpuid",
    [X86_FEATURE_EXTD_APICID]    = "extd_apicid",
    [X86_FEATURE_AMD_DCM]        = "amd_dcm",
    [X86_FEATURE_APERFMPERF]     = "aperfmperf",
    [X86_FEATURE_NONSTOP_TSC_S3]     = "nonstop_tsc_s3",
    [X86_FEATURE_TSC_KNOWN_FREQ]     = "tsc_known_freq",
    [X86_FEATURE_XMM3]       = "pni",
    [X86_FEATURE_PCLMULQDQ]      = "pclmulqdq",
    [X86_FEATURE_DTES64]         = "dtes64",
    [X86_FEATURE_MWAIT]      = "monitor",
    [X86_FEATURE_DSCPL]      = "ds_cpl",
    [X86_FEATURE_VMX]        = "vmx",
    [X86_FEATURE_SMX]        = "smx",
    [X86_FEATURE_EST]        = "est",
    [X86_FEATURE_TM2]        = "tm2",
    [X86_FEATURE_SSSE3]      = "ssse3",
    [X86_FEATURE_CID]        = "cid",
    [X86_FEATURE_SDBG]       = "sdbg",
    [X86_FEATURE_FMA]        = "fma",
    [X86_FEATURE_CX16]       = "cx16",
    [X86_FEATURE_XTPR]       = "xtpr",
    [X86_FEATURE_PDCM]       = "pdcm",
    [X86_FEATURE_PCID]       = "pcid",
    [X86_FEATURE_DCA]        = "dca",
    [X86_FEATURE_XMM4_1]         = "sse4_1",
    [X86_FEATURE_XMM4_2]         = "sse4_2",
    [X86_FEATURE_X2APIC]         = "x2apic",
    [X86_FEATURE_MOVBE]      = "movbe",
    [X86_FEATURE_POPCNT]         = "popcnt",
    [X86_FEATURE_TSC_DEADLINE_TIMER] = "tsc_deadline_timer",
    [X86_FEATURE_AES]        = "aes",
    [X86_FEATURE_XSAVE]      = "xsave",
    [X86_FEATURE_AVX]        = "avx",
    [X86_FEATURE_F16C]       = "f16c",
    [X86_FEATURE_RDRAND]         = "rdrand",
    [X86_FEATURE_HYPERVISOR]     = "hypervisor",
    [X86_FEATURE_XSTORE]         = "rng",
    [X86_FEATURE_XSTORE_EN]      = "rng_en",
    [X86_FEATURE_XCRYPT]         = "ace",
    [X86_FEATURE_XCRYPT_EN]      = "ace_en",
    [X86_FEATURE_ACE2]       = "ace2",
    [X86_FEATURE_ACE2_EN]        = "ace2_en",
    [X86_FEATURE_PHE]        = "phe",
    [X86_FEATURE_PHE_EN]         = "phe_en",
    [X86_FEATURE_PMM]        = "pmm",
    [X86_FEATURE_PMM_EN]         = "pmm_en",
    [X86_FEATURE_LAHF_LM]        = "lahf_lm",
    [X86_FEATURE_CMP_LEGACY]     = "cmp_legacy",
    [X86_FEATURE_SVM]        = "svm",
    [X86_FEATURE_EXTAPIC]        = "extapic",
    [X86_FEATURE_CR8_LEGACY]     = "cr8_legacy",
    [X86_FEATURE_ABM]        = "abm",
    [X86_FEATURE_SSE4A]      = "sse4a",
    [X86_FEATURE_MISALIGNSSE]    = "misalignsse",
    [X86_FEATURE_3DNOWPREFETCH]  = "3dnowprefetch",
    [X86_FEATURE_OSVW]       = "osvw",
    [X86_FEATURE_IBS]        = "ibs",
    [X86_FEATURE_XOP]        = "xop",
    [X86_FEATURE_SKINIT]         = "skinit",
    [X86_FEATURE_WDT]        = "wdt",
    [X86_FEATURE_LWP]        = "lwp",
    [X86_FEATURE_FMA4]       = "fma4",
    [X86_FEATURE_TCE]        = "tce",
    [X86_FEATURE_NODEID_MSR]     = "nodeid_msr",
    [X86_FEATURE_TBM]        = "tbm",
    [X86_FEATURE_TOPOEXT]        = "topoext",
    [X86_FEATURE_PERFCTR_CORE]   = "perfctr_core",
    [X86_FEATURE_PERFCTR_NB]     = "perfctr_nb",
    [X86_FEATURE_BPEXT]      = "bpext",
    [X86_FEATURE_PTSC]       = "ptsc",
    [X86_FEATURE_PERFCTR_LLC]    = "perfctr_llc",
    [X86_FEATURE_MWAITX]         = "mwaitx",
    [X86_FEATURE_RING3MWAIT]     = "ring3mwait",
    [X86_FEATURE_CPUID_FAULT]    = "cpuid_fault",
    [X86_FEATURE_CPB]        = "cpb",
    [X86_FEATURE_EPB]        = "epb",
    [X86_FEATURE_CAT_L3]         = "cat_l3",
    [X86_FEATURE_CAT_L2]         = "cat_l2",
    [X86_FEATURE_CDP_L3]         = "cdp_l3",
    [X86_FEATURE_INVPCID_SINGLE]     = "invpcid_single",
    [X86_FEATURE_HW_PSTATE]      = "hw_pstate",
    [X86_FEATURE_PROC_FEEDBACK]  = "proc_feedback",
    [X86_FEATURE_SME]        = "sme",
    [X86_FEATURE_PTI]        = "pti",
    [X86_FEATURE_INTEL_PPIN]     = "intel_ppin",
    [X86_FEATURE_INTEL_PT]       = "intel_pt",
    [X86_FEATURE_AVX512_4VNNIW]  = "avx512_4vnniw",
    [X86_FEATURE_AVX512_4FMAPS]  = "avx512_4fmaps",
    [X86_FEATURE_MBA]        = "mba",
    [X86_FEATURE_TPR_SHADOW]     = "tpr_shadow",
    [X86_FEATURE_VNMI]       = "vnmi",
    [X86_FEATURE_FLEXPRIORITY]   = "flexpriority",
    [X86_FEATURE_EPT]        = "ept",
    [X86_FEATURE_VPID]       = "vpid",
    [X86_FEATURE_VMMCALL]        = "vmmcall",
    [X86_FEATURE_FSGSBASE]       = "fsgsbase",
    [X86_FEATURE_TSC_ADJUST]     = "tsc_adjust",
    [X86_FEATURE_BMI1]       = "bmi1",
    [X86_FEATURE_HLE]        = "hle",
    [X86_FEATURE_AVX2]       = "avx2",
    [X86_FEATURE_SMEP]       = "smep",
    [X86_FEATURE_BMI2]       = "bmi2",
    [X86_FEATURE_ERMS]       = "erms",
    [X86_FEATURE_INVPCID]        = "invpcid",
    [X86_FEATURE_RTM]        = "rtm",
    [X86_FEATURE_CQM]        = "cqm",
    [X86_FEATURE_MPX]        = "mpx",
    [X86_FEATURE_RDT_A]      = "rdt_a",
    [X86_FEATURE_AVX512F]        = "avx512f",
    [X86_FEATURE_AVX512DQ]       = "avx512dq",
    [X86_FEATURE_RDSEED]         = "rdseed",
    [X86_FEATURE_ADX]        = "adx",
    [X86_FEATURE_SMAP]       = "smap",
    [X86_FEATURE_AVX512IFMA]     = "avx512ifma",
    [X86_FEATURE_CLFLUSHOPT]     = "clflushopt",
    [X86_FEATURE_CLWB]       = "clwb",
    [X86_FEATURE_AVX512PF]       = "avx512pf",
    [X86_FEATURE_AVX512ER]       = "avx512er",
    [X86_FEATURE_AVX512CD]       = "avx512cd",
    [X86_FEATURE_SHA_NI]         = "sha_ni",
    [X86_FEATURE_AVX512BW]       = "avx512bw",
    [X86_FEATURE_AVX512VL]       = "avx512vl",
    [X86_FEATURE_XSAVEOPT]       = "xsaveopt",
    [X86_FEATURE_XSAVEC]         = "xsavec",
    [X86_FEATURE_XGETBV1]        = "xgetbv1",
    [X86_FEATURE_XSAVES]         = "xsaves",
    [X86_FEATURE_CQM_LLC]        = "cqm_llc",
    [X86_FEATURE_CQM_OCCUP_LLC]  = "cqm_occup_llc",
    [X86_FEATURE_CQM_MBM_TOTAL]  = "cqm_mbm_total",
    [X86_FEATURE_CQM_MBM_LOCAL]  = "cqm_mbm_local",
    [X86_FEATURE_CLZERO]         = "clzero",
    [X86_FEATURE_IRPERF]         = "irperf",
    [X86_FEATURE_XSAVEERPTR]     = "xsaveerptr",
    [X86_FEATURE_DTHERM]         = "dtherm",
    [X86_FEATURE_IDA]        = "ida",
    [X86_FEATURE_ARAT]       = "arat",
    [X86_FEATURE_PLN]        = "pln",
    [X86_FEATURE_PTS]        = "pts",
    [X86_FEATURE_HWP]        = "hwp",
    [X86_FEATURE_HWP_NOTIFY]     = "hwp_notify",
    [X86_FEATURE_HWP_ACT_WINDOW]     = "hwp_act_window",
    [X86_FEATURE_HWP_EPP]        = "hwp_epp",
    [X86_FEATURE_HWP_PKG_REQ]    = "hwp_pkg_req",
    [X86_FEATURE_NPT]        = "npt",
    [X86_FEATURE_LBRV]       = "lbrv",
    [X86_FEATURE_SVML]       = "svm_lock",
    [X86_FEATURE_NRIPS]      = "nrip_save",
    [X86_FEATURE_TSCRATEMSR]     = "tsc_scale",
    [X86_FEATURE_VMCBCLEAN]      = "vmcb_clean",
    [X86_FEATURE_FLUSHBYASID]    = "flushbyasid",
    [X86_FEATURE_DECODEASSISTS]  = "decodeassists",
    [X86_FEATURE_PAUSEFILTER]    = "pausefilter",
    [X86_FEATURE_PFTHRESHOLD]    = "pfthreshold",
    [X86_FEATURE_AVIC]       = "avic",
    [X86_FEATURE_V_VMSAVE_VMLOAD]    = "v_vmsave_vmload",
    [X86_FEATURE_VGIF]       = "vgif",
    [X86_FEATURE_AVX512VBMI]     = "avx512vbmi",
    [X86_FEATURE_UMIP]       = "umip",
    [X86_FEATURE_PKU]        = "pku",
    [X86_FEATURE_OSPKE]      = "ospke",
    [X86_FEATURE_AVX512_VBMI2]   = "avx512_vbmi2",
    [X86_FEATURE_GFNI]       = "gfni",
    [X86_FEATURE_VAES]       = "vaes",
    [X86_FEATURE_VPCLMULQDQ]     = "vpclmulqdq",
    [X86_FEATURE_AVX512_VNNI]    = "avx512_vnni",
    [X86_FEATURE_AVX512_BITALG]  = "avx512_bitalg",
    [X86_FEATURE_AVX512_VPOPCNTDQ]   = "avx512_vpopcntdq",
    [X86_FEATURE_LA57]       = "la57",
    [X86_FEATURE_RDPID]      = "rdpid",
    [X86_FEATURE_OVERFLOW_RECOV]     = "overflow_recov",
    [X86_FEATURE_SUCCOR]         = "succor",
    [X86_FEATURE_SMCA]       = "smca",
};

const char * const x86_bug_flags[NBUGINTS*32] = {
    [X86_BUG_F00F - NCAPINTS*32]        = "f00f",
    [X86_BUG_FDIV - NCAPINTS*32]        = "fdiv",
    [X86_BUG_COMA - NCAPINTS*32]        = "coma",
    [X86_BUG_AMD_TLB_MMATCH - NCAPINTS*32]  = "tlb_mmatch",
    [X86_BUG_AMD_APIC_C1E - NCAPINTS*32]    = "apic_c1e",
    [X86_BUG_11AP - NCAPINTS*32]        = "11ap",
    [X86_BUG_FXSAVE_LEAK - NCAPINTS*32]     = "fxsave_leak",
    [X86_BUG_CLFLUSH_MONITOR - NCAPINTS*32]     = "clflush_monitor",
    [X86_BUG_SYSRET_SS_ATTRS - NCAPINTS*32]     = "sysret_ss_attrs",
    [X86_BUG_NULL_SEG - NCAPINTS*32]    = "null_seg",
    [X86_BUG_SWAPGS_FENCE - NCAPINTS*32]    = "swapgs_fence",
    [X86_BUG_MONITOR - NCAPINTS*32]         = "monitor",
    [X86_BUG_AMD_E400 - NCAPINTS*32]    = "amd_e400",
    [X86_BUG_CPU_MELTDOWN - NCAPINTS*32]    = "cpu_meltdown",
};

そして、このファイルがどのように作られるかと言うと、./arch/x86/kernel/cpu/Makefileの以下の部分です。mkcapflags.shというシェルスクリプトを実行してそこからファイルを作ってます。

ifdef CONFIG_X86_FEATURE_NAMES
quiet_cmd_mkcapflags = MKCAP   $@
      cmd_mkcapflags = $(CONFIG_SHELL) $(srctree)/$(src)/mkcapflags.sh $< $@

cpufeature = $(src)/../../include/asm/cpufeatures.h

targets += capflags.c
$(obj)/capflags.c: $(cpufeature) $(src)/mkcapflags.sh FORCE
    $(call if_changed,mkcapflags)
endif

まとめ

flagsに表示する機能の名前は配列で定義されていて、その配列はカーネルコンパイル中に作られます。