Blog posts by @retrage01

mirror of https://retrage.github.io/

LLVMのEFI Byte Codeバックエンドを作る

ここでは開発しているLLVMEFI Byte Code (EBC)バックエンドの概要と EBCバックエンド固有の問題などについてみていく.

ソースコードは以下で公開している.

続きを読む

文鎮化したMinnowboardを復旧させる

MinnowboardIntel Atomを搭載した シングルボードコンピュータである. Minnowboardを使った実験で誤ったファームウェアを書き込んでしまい brick (文鎮化) させてしまったので SPI Flashを外部から書き換えることで復旧させる.

続きを読む

Linux kernelをUnikernelのライブラリ化する

Linux kernelをUnikraftのライブラリ化した. これにより機能の少ないUnikernelの資源として Linux kernelの持つ機能を利用することが可能となる.

続きを読む

正規表現からLLVM IRを生成する

正規表現は文字列マッチングなどに広く用いられている形式言語であるが, 等価な決定性有限オートマトン(DFA)に変換できることが知られている. google/redgrep は与えられた正規表現から等価なDFAに相当するネイティブコードを LLVMにより生成する. ここではredgrepを改造して 正規表現からどのようなLLVM IRが生成されるのかをみてみる.

redllというツールを追加した.コードは

に置いてある.

使い方

最初にLLVMソースコードをダウンロードして ビルドしてローカルの適当な場所にインストールしておく. redgrepは新しいLLVMを要求するのでLLVM 8.0.0あたりを入れておく. redgrepのビルドが通るようにパスを通す.

$ export LLVM_CONFIG=/path/to/bin/llvm-config

とかやってからredgrepをビルドする.

$ ./redll "regex"

で遊べる.

例: "a"

試しに正規表現パターン"a"がどのようになるのかを以下に示す.

$ ./redll "a"
; dfa is 3 states
; ModuleID = 'M'
source_filename = "M"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

; Function Attrs: norecurse nounwind readonly
define i1 @F(i8* nocapture readonly, i64) local_unnamed_addr #0 {
entry:
  %2 = icmp eq i64 %1, 0
  br i1 %2, label %return_true, label %3

return_true:                                      ; preds = %3, %entry
  ret i1 false

; <label>:3:                                      ; preds = %entry
  %4 = load i8, i8* %0, align 1
  %cond = icmp eq i8 %4, 97
  br i1 %cond, label %5, label %return_true

; <label>:5:                                      ; preds = %3
  %6 = icmp eq i64 %1, 1
  ret i1 %6
}

attributes #0 = { norecurse nounwind readonly }

ここではFという関数があり, 入力が0であればfalseを返し, そうでなければ入力から1文字取り出し 97=aであれば1=trueを返す, というような状態機械になっていることがわかる.

まとめ

他にも色々面白く遊べるはずなので試してほしい.

参考文献

技術書典6で新刊「UEFI読本 GRUB編」を頒布

2019年4月14日に池袋サンシャインシティで開催される 技術書典6において「海洋軟件」として 新刊「UEFI読本 GRUB編」を頒布する。 既刊「UEFI読本 基礎編 Linux編」も基礎編を分離し 「UEFI読本 Linux編」として頒布する。 配置先は「う27」である。

続きを読む

春の秋山郷の行き方 (2019年3月版)

2019年3月21日から24日かけて開催された「カーネルVMキャンプ #9」[1]に参加した. 開催場所は秋山郷結東温泉「かたくりの宿」という元小学校校舎の宿[2]である. 東京から「かたくりの宿」までの公共交通機関でのアクセスがやや変則的なのでここにまとめておく.

概要

「かたくりの宿」のWebページに書かれている通り,東京からは以下の通りの行程となる[3].

  • 東京 - (上越新幹線) -> 越後湯沢
  • 越後湯沢 - (急行バス森宮野原行) -> 津南役場前
  • 津南役場前 - (見玉行バス) -> 見玉
  • 見玉 - (デマンドタクシー) -> かたくりの宿

前日17:00までにタクシーの予約

基本この通りに行けばいいのだが, デマンドタクシーを前日17:00までに予約する必要がある[4]. タクシーとはいうものの,1日3本の片道300円の路線バスの延長のようなものである. なお,私はこの事実に前日16:58に気づき電話で予約した.危なかった.

越後湯沢から見玉まで

JR越後湯沢駅を降り,東口4番から急行森宮野原行のバズに乗車,津南役場前で降車. 津南役場前から見玉行のバスに乗車,見玉で降車. ここまでは案内の通りに行けるはずである. なお,電子マネーは利用できず,かつ五千円札や一万円札での支払いは車内でできないため, 事前に崩して小銭を作るか乗車券を案内所で購入しておく必要がある.

f:id:retrage01:20190326022106j:plain
越後湯沢東口 右奥が4番乗り場

f:id:retrage01:20190326022121j:plain
津南役場前バス停 ここで見玉行を待つ

見玉からかたくりの宿まで

事前に予約したデマンドタクシーが来るはずなので,それに乗ればかたくりの宿まで連れて行ってくれる. デマンドタクシーは見玉までのバスから乗り継ぎができるように時刻表が組まれている. なので,例えば11:49見玉着であれば11:50のデマンドタクシーはバスの到着を待ってくれるはずである.

f:id:retrage01:20190326022209j:plain
見玉 左に自販機と土産物屋がある

f:id:retrage01:20190326022228j:plain
見玉バス停

私の場合

しかし,私はこの事実に気づかなかったため,遅い14:30見玉発のデマンドタクシーを予約してしまい, かつ11:49見玉着のバスに乗ってしまったため,2時間半ほど見玉でデマンドタクシーを待つことになった. 完全にアホである.

見玉には見玉不動尊とその周辺に土産物屋がいくつかある. が,冬季のためか土産物屋は全て閉まっており,かつ見玉不動尊も雪で覆われていて奥まで見ることができなかった. 幸いなことにdocomo回線は繋がっていて,バス停の裏の屋根のある場所にベンチを見つけたため, その場で作業をしつつ[5][6]バスを待つことができた.

f:id:retrage01:20190326022237j:plainf:id:retrage01:20190326022252j:plainf:id:retrage01:20190326022302j:plainf:id:retrage01:20190326022309j:plain
見玉不動尊 雪で奥まで行けなかった

f:id:retrage01:20190326022329j:plain
バス停裏にあったスペース ここのベンチで作業しつつバスを待った

参考文献

LinuxにおけるEFI Variableをみてみる

この記事は Linux Advent Calendar 201820日目の記事として書かれた. ここではLinux kerenlにおけるEFI Variableのコードをみていく.

EFI Variableとは

UEFIでは,EFI varibale(EFI変数)というものが存在する. これは不揮発性メモリ(NVRAM)に値が書き込まれるため, 電源を切っても値が失われることなく保存される. EFI Variableは起動時の起動の順番などが保存される.

UEFIにはEFI Variablesへの読み書きを行うための機能が Runtime Servicesに存在する. このため後述するように,OSの起動後もEFI Variableが Runtime Servicesを通して利用可能となっている. 関連する関数は具体的には以下の4つである.

  • GetVariables
  • GetNextVariablesName
  • SetVariables
  • QueryVairableInfo

EFI Variableはkey-valueの形式になっており, 文字列keyを入力として,GetVariables/SetVariablesにより valueの読み/書きができる. Valueを得るために必要なkeyはGetNextVariableName により得ることができる. QueryVairbaleInfoではEFI Variable全体のについての情報を得ることができる.

Linux kernelにおけるEFI Variable

Linux kernelでは,EFI Variableは 擬似ファイルシステムとして表現され, /sys/firmware/efi/efivarsにマウントされる. (なお,過去にはsysfs経由でのサポートだったようだが, sysfsの制限のため,別実装となったようである)

ここで実際のefivarsを例示してみる.

$ ls /sys/firmware/efi/efivars
# snip
Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c
Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c
Boot0002-8be4df61-93ca-11d2-aa0d-00e098032b8c
Boot0003-8be4df61-93ca-11d2-aa0d-00e098032b8c
# snip
$ cat /sys/firmware/efi/efivars/Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c
bubuntu??H??yC??Y??ä?4\EFI\UBUNTU\SHIMX64.EFI?%

このように,0000には現在起動しているUbuntuについてのパスが保存されていることがわかる.

efivarsの実装

ではここから実際にefivarsの実装をみていく. トップダウンefivarfsの実装から どのようにEFI Variableがファイルにマップされるかを見て, 次にRuntime Servicesがどのように呼ばれるかを見ていく. 参照するLnux kernelはmainlineのv4.20-rc2(ccda4af0f4b92f7b4c308d3acc262f4a7e3affad) である.

efivarfsの初期化

efivarfsの実装はfs/efivarfs/にある. 最初にfs/efivarfs/super.cをみる.

static __init int efivarfs_init(void)
{
    if (!efi_enabled(EFI_RUNTIME_SERVICES))
        return -ENODEV;

    if (!efivars_kobject())
        return -ENODEV;

    return register_filesystem(&efivarfs_type);
}

Linux kernel初期化時にefivarfsの初期化が efivarfs_initにより行われてefivarfsがマウントされる.

static struct file_system_type efivarfs_type = { 
    .owner   = THIS_MODULE,
    .name    = "efivarfs",
    .mount   = efivarfs_mount,
    .kill_sb = efivarfs_kill_sb,
};
static struct dentry *efivarfs_mount(struct file_system_type *fs_type,
                    int flags, const char *dev_name, void *data)
{   
    return mount_single(fs_type, flags, data, efivarfs_fill_super);
}

辿っていくと,efivarfs_fill_supermount_singleにより実行される.

static int efivarfs_fill_super(struct super_block *sb, void *data, int silent)
{   
    struct inode *inode = NULL;
    struct dentry *root;
    int err;
    
    efivarfs_sb = sb;
    
    sb->s_maxbytes          = MAX_LFS_FILESIZE;
    sb->s_blocksize         = PAGE_SIZE;
    sb->s_blocksize_bits    = PAGE_SHIFT;
    sb->s_magic             = EFIVARFS_MAGIC;
    sb->s_op                = &efivarfs_ops;
    sb->s_d_op      = &efivarfs_d_ops;
    sb->s_time_gran         = 1;
    
    inode = efivarfs_get_inode(sb, NULL, S_IFDIR | 0755, 0, true);
    if (!inode)
        return -ENOMEM;
    inode->i_op = &efivarfs_dir_inode_operations;
    
    root = d_make_root(inode);
    sb->s_root = root;
    if (!root) 
        return -ENOMEM;
    
    INIT_LIST_HEAD(&efivarfs_list);
    
    err = efivar_init(efivarfs_callback, (void *)sb, true, &efivarfs_list);
    if (err)
        __efivar_entry_iter(efivarfs_destroy, &efivarfs_list, NULL, NULL);
    
    return err;
}

ここでファイルシステムの中身を作成している. 最初に/sys/firmware/efi/efivarが作成され, efivar_initによりefivarfs_listEFI Variableのエントリが追加される.

/**
 * efivar_init - build the initial list of EFI variables
 * @func: callback function to invoke for every variable
 * @data: function-specific data to pass to @func
 * @atomic: do we need to execute the @func-loop atomically?
 * @duplicates: error if we encounter duplicates on @head?
 * @head: initialised head of variable list
 *
 * Get every EFI variable from the firmware and invoke @func. @func
 * should call efivar_entry_add() to build the list of variables.
 *
 * Returns 0 on success, or a kernel error code on failure.
 */
int efivar_init(int (*func)(efi_char16_t *, efi_guid_t, unsigned long, void *),
        void *data, bool duplicates, struct list_head *head)
{
    const struct efivar_operations *ops = __efivars->ops;
    unsigned long variable_name_size = 1024; 
    efi_char16_t *variable_name;
    efi_status_t status;
    efi_guid_t vendor_guid;
    int err = 0;
    /* snip */
    do {
        variable_name_size = 1024;

        status = ops->get_next_variable(&variable_name_size,
                        variable_name,
                        &vendor_guid);
        switch (status) {
        case EFI_SUCCESS:
            if (duplicates)
                up(&efivars_lock);

            variable_name_size = var_name_strnsize(variable_name,
                                   variable_name_size);

            /*
             * Some firmware implementations return the
             * same variable name on multiple calls to
             * get_next_variable(). Terminate the loop
             * immediately as there is no guarantee that
             * we'll ever see a different variable name,
             * and may end up looping here forever.
             */
            if (duplicates &&
                variable_is_present(variable_name, &vendor_guid,
                        head)) {
                dup_variable_bug(variable_name, &vendor_guid,
                         variable_name_size);
                status = EFI_NOT_FOUND;
            } else {
                err = func(variable_name, vendor_guid,
                       variable_name_size, data);
                if (err)
                    status = EFI_NOT_FOUND;
            }

            if (duplicates) {
                if (down_interruptible(&efivars_lock)) {
                    err = -EINTR;
                    goto free;
                }
            }

            break;

efivar_initでは, ops->get_next_variableにより 実際にRuntimeServices->GetNextVariableが呼ばれ, EFI Variableが読み出される. なお,コメントにあるように,いくつかのUEFI Firmwareの実装では 複数のGetNextVariableの呼び出しにより同じEFI Variableが得られてしまう というバグがあるため,重複があった場合,dup_variable_bugにより これを処理し,do-whileを抜けるようになっているようである.

さて,問題がない場合は,引数として渡されるfuncを呼び出す. これはefivarfs_callbackとなっている.

static int efivarfs_callback(efi_char16_t *name16, efi_guid_t vendor,
                 unsigned long name_size, void *data)
{
    struct super_block *sb = (struct super_block *)data;
    struct efivar_entry *entry;
    struct inode *inode = NULL;
    struct dentry *dentry, *root = sb->s_root;
    unsigned long size = 0;
    char *name;
    int len;
    int err = -ENOMEM;
    bool is_removable = false;
    /* snip */
    inode = efivarfs_get_inode(sb, d_inode(root), S_IFREG | 0644, 0,
                   is_removable);
    if (!inode)
        goto fail_name;

    dentry = efivarfs_alloc_dentry(root, name);
    if (IS_ERR(dentry)) {
        err = PTR_ERR(dentry);
        goto fail_inode;
    }

    efivar_entry_size(entry, &size);
    err = efivar_entry_add(entry, &efivarfs_list);
    if (err)
        goto fail_inode;

efivarfs_callbackでは, 得られたEFI Variableに対応するファイルを作成して追加する.

以上がefivarfsの初期化の流れとなっている.

efivarfsのread/write

次にefivarfsでのread/write時の動作をみてみる.

const struct file_operations efivarfs_file_operations = {
    .open   = simple_open,
    .read   = efivarfs_file_read,
    .write  = efivarfs_file_write,
    .llseek = no_llseek,
    .unlocked_ioctl = efivarfs_file_ioctl,
}

より,efivarfs_file_readefivarfs_file_writeに集約されている.

read時

static ssize_t efivarfs_file_read(struct file *file, char __user *userbuf,
        size_t count, loff_t *ppos)
{
    struct efivar_entry *var = file->private_data;
    unsigned long datasize = 0;
    u32 attributes;
    void *data;
    ssize_t size = 0;
    int err;
    /* snip */
    size = efivar_entry_get(var, &attributes, &datasize,
                data + sizeof(attributes));
int efivar_entry_get(struct efivar_entry *entry, u32 *attributes,
             unsigned long *size, void *data)
{
    const struct efivar_operations *ops = __efivars->ops;
    efi_status_t status;

    if (down_interruptible(&efivars_lock))
        return -EINTR;
    status = ops->get_variable(entry->var.VariableName,
                   &entry->var.VendorGuid,
                   attributes, size, data);
    up(&efivars_lock);

    return efi_status_to_err(status);
}

このように, efivarfs_file_read->efivar_entry_get->get_variable によりread時にRuntimeServices->GetVariableを呼び出していることがわかる.

write時

static ssize_t efivarfs_file_write(struct file *file,
        const char __user *userbuf, size_t count, loff_t *ppos)
{
    struct efivar_entry *var = file->private_data;
    void *data;
    u32 attributes;
    struct inode *inode = file->f_mapping->host;
    unsigned long datasize = count - sizeof(attributes);
    ssize_t bytes;
    bool set = false;
    /* snip */
    bytes = efivar_entry_set_get_size(var, attributes, &datasize,
                      data, &set);
    /* snip */
    if (bytes == -ENOENT) {
        drop_nlink(inode);
        d_delete(file->f_path.dentry);
        dput(file->f_path.dentry);
    } else {
        inode_lock(inode);
        i_size_write(inode, datasize + sizeof(attributes));
        inode_unlock(inode);
    } 
int efivar_entry_set_get_size(struct efivar_entry *entry, u32 attributes,
                  unsigned long *size, void *data, bool *set)
{
    const struct efivar_operations *ops = __efivars->ops;
    efi_char16_t *name = entry->var.VariableName;
    efi_guid_t *vendor = &entry->var.VendorGuid;
    efi_status_t status;
    int err;
    /* snip */
    status = ops->set_variable(name, vendor, attributes, *size, data);
    if (status != EFI_SUCCESS) {
        err = efi_status_to_err(status);
        goto out;
    }
    *size = 0;

    /*
     * Writing to the variable may have caused a change in size (which
     * could either be an append or an overwrite), or the variable to be
     * deleted. Perform a GetVariable() so we can tell what actually
     * happened.
     */
    status = ops->get_variable(entry->var.VariableName,
                   &entry->var.VendorGuid,
                   NULL, size, NULL);

    if (status == EFI_NOT_FOUND)
        efivar_entry_list_del_unlock(entry);
    else
        up(&efivars_lock);

write時も同様にして efivarfs_file_write->efivar_entry_set_get_size->set_variable により与えられたデータをEFI Variableとして書き込んでいる. コメントにあるように,RuntimeServices->GetVariableを実行し, データの追加や上書き,削除のどれがなされたかをsizeにより判定する. efivar_entry_set_get_sizeの返り値bytesにより, ファイルを削除するか,サイズを変更するなどの操作を行う.

Runtime Servicesの呼び出しに関するTips

static efi_status_t virt_efi_get_variable(efi_char16_t *name,
                      efi_guid_t *vendor,
                      u32 *attr,
                      unsigned long *data_size,
                      void *data)
{
    efi_status_t status;

    if (down_interruptible(&efi_runtime_lock))
        return EFI_ABORTED;
    status = efi_queue_work(GET_VARIABLE, name, vendor, attr, data_size,
                data);
    up(&efi_runtime_lock);
    return status;
}

Linux kernelにおいて, UEFI Runtime Servicesを呼び出す場合には, efi_runtime_lockセマフォをdownしてから 実行すべきUEFIサービスをefi_queue_workキューに挿入して 実行させ,最後にセマフォをupさせている. (例外もあり,nonblockingな呼び出しも存在する)

OSを自作する際にも参考にしたい工夫である.

ebcvm: A Usermode EFI Byte Code Virtual Machine

この記事は 自作OS Advent Calendar 2018 の19日目の記事として書かれた. ここでは,フルスクラッチで開発したEFI Byte Code Virtual Machineである ebcvm[1]と開発したのELVMのEBCバックエンド[2]について紹介する.

続きを読む

EFI Byte Code解説

本記事は2018年11月10日に開催されたkernelvm 北陸 Part4において 発表した内容[10]をまとめたものである. ここではUEFIの持つ独自のバイトコードであるEFI Byte CodeとそのVMについてみていく.

続きを読む