strlen for string literal

Environment

Sample code

#include <string.h>

size_t string_const_size() {
    return strlen("hello world");
}

Compile with gcc -O0

gcc -O0 -c strlen.c
objdump -S strlen.o
strlen.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <string_const_size>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   b8 0b 00 00 00          mov    $0xb,%eax ;; 0xb=11 is string length of "hello world"
   d:   5d                      pop    %rbp
   e:   c3                      retq   

strlen for string literal is calculated at compiling time even if optimization is disabled

Compile with clang -O0

I got same result as gcc

strlen.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <string_const_size>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 0b 00 00 00          mov    $0xb,%eax ;; here
   9:   5d                      pop    %rbp
   a:   c3                      retq   
   b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Compile with gcc -O0 -fno-builtin

It looks strlen is called

strlen.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <string_const_size>:
   0:   f3 0f 1e fa             endbr64 
   4:   55                      push   %rbp
   5:   48 89 e5                mov    %rsp,%rbp
   8:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # f <string_const_size+0xf>
   f:   e8 00 00 00 00          callq  14 <string_const_size+0x14>
  14:   5d                      pop    %rbp
  15:   c3                      retq   

Summary

I suppose I can use strlen for litaral string without runtime overhead and I don't need hack as below

const size_t some_length = sizeof("Hello World") -1;

Build GCC 4.9.4 x86_64 on Ubuntu 19.10

Apply patch

libgcc of gcc4.9.4 has issue on newer environment

diff -ur gcc-4.9.4/libgcc/config/i386/linux-unwind.h new-gcc-4.9.4/libgcc/config/i386/linux-unwind.h
--- gcc-4.9.4/libgcc/config/i386/linux-unwind.h   2014-01-03 07:25:22.000000000 +0900
+++ new-gcc-4.9.4/libgcc/config/i386/linux-unwind.h   2020-04-17 11:25:54.600423882 +0900
@@ -58,7 +58,7 @@
   if (*(unsigned char *)(pc+0) == 0x48
       && *(unsigned long long *)(pc+1) == RT_SIGRETURN_SYSCALL)
     {
-      struct ucontext *uc_ = context->cfa;
+      ucontext_t *uc_ = context->cfa;
       /* The void * cast is necessary to avoid an aliasing warning.
          The aliasing warning is correct, but should not be a problem
          because it does not alias anything.  */
@@ -138,7 +138,7 @@
    siginfo_t *pinfo;
    void *puc;
    siginfo_t info;
-  struct ucontext uc;
+   ucontext_t ucontext uc;
       } *rt_ = context->cfa;
       /* The void * cast is necessary to avoid an aliasing warning.
          The aliasing warning is correct, but should not be a problem

Configure & make

# apply patch
cd gcc-4.9.4
mkdir build
cd build
../configure --prefix=$HOME/local/gcc-4.9 --enable-languages=c --disable-multilib --disable-libsanitizer
make
make install

libsanitaizer of gcc 4.9.4 cannot be built with recent glibc.

Minimum setup for node native module development with VScode

It is difficult to develop node native module without IDE. Because V8 API is difficult to use, complicated, often changed. node-gyp can generate Visual Studio solution file but other IDE is not supported well. I show how to setup writing native module on VScode

Install node headers

Install header files of node.js via node-gyp configure

node header is installed under user cache directory

  • Windows %LOCALAPPDATA%\Cache\node-gyp
  • macOS ~/Library/Caches/node-gyp
  • Linux ~/.cache/node-gyp

Set include paths

Do C/C++: Edit configurations (JSON) via command palette and open c_cpp_properties.json.

Add above header path to includePath list

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "${env:HOME}/.cache/node-gyp/12.16.1/include/node" // 12.16.1 is node version, you need to change to your node version
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

Screenshot

f:id:syohex:20200405153245g:plain

Enjoy

Disable Git-Bash case-insensitive completion

  1. Open git bash or command prompt as Admin
  2. Go to C:\Program Files\Git\etc
  3. Open inputrc file
  4. Change default completion-ignore-case value as below
--- inputrc.old 2019-12-13 20:32:51.577869500 +0900
+++ inputrc     2019-12-13 20:32:40.337057100 +0900
@@ -9,7 +9,7 @@
 # Ignore case for the command-line-completion functionality
 # on:  default on a Windows style console
 # off: default on a *nix style console
-set completion-ignore-case on
+set completion-ignore-case off

 # disable/enable 8bit input
 set input-meta on

zipパーサに関するメモ

仕事で zipファイルを展開せずバイナリのまま構造解析する可能性があったため, zipの仕様を調べていました. 仕様については Wikipedia(日本語版は今ひとつわからなかったところがあったので英語版をメインで見た方がよいと思います)等を参照してください. で, パース方法を調べると初めにファイル末尾にある End of central directory record(以下 EOCD)を探して, そこから Central directory file headerを探してごにょごにょするとあったのですが, EOCDには可変長なコメントフィールド(最大 0xffffバイト)があり, それを考慮した上で正しくパースするにはどうすればいいのだろうと思って, 各種言語の zipライブラリを調べてみました. これはそのメモです. 本記事は zip64の内容は含みません. ご注意ください

zip形式の仕様

Zip (file format) - Wikipedia

問題となると考えたケース

  • コメント部に別の zipを書き込んだ場合正しくパースできるのか
  • validなファイルを invalidと解釈しないか
  • 期待しないファイルを展開することはないか(今回未検証)

Go言語の archive/zip

zip.OpenReaderの大雑把な流れは以下のとおりです. 調べたのは Go 1.9の archive/zipになります.

  • EOCDを探す. 探すパターンが 2つあり, 末尾から 1024バイトの位置, 65 * 1024(=66560)バイトの位置から EOCDのマジックナンバを探す.
  • マジックナンバが見つかったら, マジックナンバを除いた位置から始まるバイト列で directoryEnd構造体(EOCDに対応)を初期化する
  • EOCDから Central directory file headerの先頭オフセットを取得し, 各ファイルの情報を zip.Fileに設定する
  • 各ファイルの情報を取得するとき, Central directory file headerのマジックナンバをチェックし, 不正だと zip.ErrFormatを返す

特にコメントを意識していないようです.

テスト zipの作成

test.zip(aaa.txtを保持)のコメント部に別の incomment.zip(test.txtを保持)を書き込みます. 期待としては, aaa.txtが展開/名前が取得されます. (zipに含まれる名前が適当すぎますが, それっぽい名前にしてしまうとバイト列が変わってしまうためはじめに作ったこスクリプトを利用しています)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import zipfile

zip_file = 'test.zip'
incomment_file = 'incomment.zip'

for f in [zip_file, incomment_file]:
    if os.path.isfile(f):
        os.remove(f)

with open('test.txt', 'wb') as f:
    f.write(os.urandom(2048)) # 1024バイト以上の長さになるようにする.

incomment_zip = zipfile.ZipFile(incomment_file, mode='w')
incomment_zip.write('test.txt')
incomment_zip.close()

with open('aaa.txt', 'wb') as f:
    f.write(b'aaa')

zf = zipfile.ZipFile(zip_file, mode='w')
zf.write('aaa.txt')

with open(incomment_file, 'rb') as f:
    buf = f.read()

zf.comment = buf
zf.close()

作成した test.zipを archive/zipに与えてみる

以下のような zipに含まれるファイル一覧を表示する Goプログラムに test.zipを与えます.

package main

import (
    "archive/zip"
    "fmt"
)

func main() {
    r, err := zip.OpenReader("test.zip")
    if err != nil {
        fmt.Println(err)
        return
    }

    for _, f := range r.File {
        fmt.Println(f.Name)
    }
}

結果は以下のように validでないとエラーが返ります.

% go run main.go
zip: not a valid zip file

原因はコメント中の別 zipの Central directory file headerの offsetを元ファイルの先頭から調べたとき, Central directory file headerのマジックナンバではないためです. コメントに関する制約は特にないようなので上で作成した zipは validだと思うのですが, invalidな zipとして判断されてしまいました.

この問題を回避するためにコメント中の zipの Central directory file headerの offsetに元の zipのコメント位置の offsetを加算した値を書き込みます. 下記のカーソル位置から始まる 2byteが Central directory file headerの offsetなのでバイナリエディタ等を使って書き換えます. (上の例だと 0x0826を 0x0899に書き換えます)

f:id:syohex:20171004010648p:plain

再度コマンドを実行してみます. 思惑通りコメント中に書き込んだ zipの方を対象の zipとして認識してくれて text.txtが出力されました.

% go run main.go
test.txt

他の言語

中身までは見ていないですが, 試してみる. Goのような実装かもしれないので, 上の offset変更を適用した zipを喰わせます.

Python3(3.6.2)

import zipfile

zf = zipfile.ZipFile('test.zip', 'r')
print(zf.namelist())
% python3 test.py
['test.txt'] ## コメント中の zipを扱う

Perl(Archive::Zip 1.59)

use strict;
use warnings;

use Archive::Zip;

my $zip = Archive::Zip->new();
$zip->read('test.zip');
print $_, "\n" for $zip->memberNames();
% perl test.pl
test.txt ## コメント中の zipを扱う

unzipコマンド(6.00 Ubuntu版)

% unzip -l test.zip
Archive:  test.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
     2048  2017-10-04 01:06   test.txt
---------                     -------
     2048                     1 file

Goのようにコメント中の zipが扱われてしまうものが多いようです. 手元で正しく動いたのは, macOSの Finder(Archive Utility)だけでした.

なおこれらのプログラムは zip中のファイル名だけでファイルの中身を誤って(??)取得させようとすると Central directory file headerの offsetもそれぞれ修正する必要があるかと思います. これは未検証なので, もしかするとうまくいかないかもしれません. 余裕があれば確認しようかと思います.

最後に

  • zipパーサにはコメントを考慮していないものがある.
  • そうなっていないのはやはり速度重視だから ?
    • ファイル末尾の min(コメントの最大長, ファイルサイズ)から状態(コメント中か否か等)を調べてマジックナンバを探索していけばよさそうなものだが..
  • 最初に探すべきデータブロックに可変長なフィールドがあるのはどうなのか.

Emacs実践入門 改訂新版を読みました.

gihyo.jp

tomoyaさんから献本頂きました. ありがとうございます. 紙で置いておきたいというのと若干申し訳ないというのもあり紙版も購入しました.

感想

「実践」とつくように実践な内容です. 操作, 設定, 拡張の 3つのパートから構成されていますが, どれもほどよい長さにまとまっていて読みやすいです. その上, Emacsにこんな謎の機能あるんだとかではなく, 実際によく使う機能のことが多く書かれていると思います. Emacs自体膨大な機能から成り立っており, 標準機能だけで一冊どころではない本にできますが, それを全部書いたら尋常じゃない量になるし, 人によっては使わない機能ばっかりになってしまいます. この本では標準の機能, 標準外の機能が読みやすい形でまとまっているなぁと思いました. たまに登場するコラムもいいアクセントというか参考になると思いました.

初心者や Emacsを使っているけど 3rdパーティ製のツールを使っていないという人という人には特にいいかなと思います. Emacsと付く本はだいたい読んでいますが, 実践という意味では一番よいと思います.

初版とくらべて

だいぶ前に読んで記憶が若干あやふやですが, 拡張の章が微妙に新しくないなぁと思ったような気がするのですが, 今回の改訂版は新しくなっていて今時だなというのを感じました.

若干気になったところ(順番は適当でページ順ではありません)

  • minor-modeの有効・無効が t, 0が使われている箇所がある. 公式的には有効は正の整数(通常 +1), 無効は負の整数(通常 -1)を推奨
  • add-hookに lambdaを使っている. 紙面上の問題というのもあるけど, lambdaで指定しまうとそれを名前で参照する方法がないので関数で指定した方がよい. 特定の状況で無効化したいとき, 名前で参照できれば, それだけ remove-hookすればよいので.
  • major-modeの決定で見つからなかったら text-modeとなっているけど, fundamental-modeではないだろうか.
  • (どちらでもいいが統一されていないような)lambdaに quoteがついているところがある
  • foo.elと foo.elcがあると常に foo.elcが読まれるとあるが, load-prefer-newerに依存する. デフォルトだと nilなので, elcの方が優先されるのは確かですが

最後に

私が作成したツールも紹介されているのですが, 最近 Emacsまともに使っていないので申し訳ないですね. これを契機に Emacs感を取り戻したいと思います.

転職して 1年と 2ヶ月ほど経った

なかなか忙しい日々を過ごしています.

仕事内容

具体的なことを書いていいのかわからないので雑に書きますが…

  • サーバプログラム(APIサーバ)を Goで書いた
    • 初めての仕事でのサーバプログラムであったが, APIサーバで JSON返すだけなので, そこまで大きな苦労はせず. 仕事の進め方などは前職とはだいぶ変わったので戸惑うところはあったけど, 苦痛とかストレスが溜まるということは特になく楽しくできたと思う
  • サーバプログラムを 4-5ヶ月書いた後からはずっと C++書いている
  • IDEをメインにするようになった
    • Xcodeはあんまり印象よくないけど, Visual StudioC++コンパイラの質を除くとだいぶ良かったので
    • デバッグ周りがだいぶ便利.
    • JetBrains All Products Packを私的に購入
    • 最近少し JSも書いたが, WebStormを使っている

転職について

結果としては良かったと思う. 自分自身も新しい刺激を受けられたというのと, 自分の前職の知識をいくらか広めることができたということが, 特に. ずっと同じことをして何かをずっと伸ばしていくというのもキャリアとしてもちろんあることだと思うけど, 新しいことを学び, 自分の知識を別の場所で広げていくというのもいいなぁと思えました. 社会的にそういうのが広まっていけば, 魅力的な職場というのも増えていくだろうので. 絶対しろとはいいませんけど, 考えてみるのはいいことなのではないかと思います.