task4233のめも

書きたいことをつらつらと

NTT Performance Tuning Challenge 2020 参加記

はじめに

2020年9月11日(金)に開催されたNTT Performance Tuning ChallengeにチームКошкаとしてはなかずさんと2人で参加しました。 終了2時間前くらいには結構上位に食い込めていたのですが、最終的にその状態に復元することが出来ず、結果正の得点を取ったチームの中で下から2番目となってしまいました。

とはいえ、今回のイベントにおいて学べたことが多かったので、ここに記すことにしました。

学べたこと

Slow Queryの探し方

Slow Queryを探すためにはpt-query-digestを使います。このツールはデータベースのSlow Queryログを解析するツールです。 Slow Queryは下記の記述をmysqldのconfファイルに追記することで出力できます。

slow_query_log       = 1
slow_query_log_file = /tmp/slow-query.log
long_query_time = 0.1

ここで出力されたログをpt-query-digestに渡すことで、下記のようにSlow Queryをまとめて出力してくれます。

$ pt-query-digest /tmp/slow-query.log
# (snip)
# Query 1: 2.41 QPS, 0.19x concurrency, ID 0xB09A20EF29F18029 at byte 84332154
# Scores: V/M = 0.07
# Time range: 2020-09-11T04:23:45 to 2020-09-11T04:25:04
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count          1     190
# Exec time     51     15s    74us   327ms    78ms   266ms    76ms    48ms
# Lock time      3     8ms    14us   381us    42us    98us    37us    31us
# Rows sent      0     188       0       1    0.99    0.99    0.10    0.99
# Rows examine   0     188       0       1    0.99    0.99    0.10    0.99
# Query size     0   8.11k      41      46   43.71   42.48    0.54   42.48
# String:
# Databases    app
# Hosts        competition-068.c.sys0098096-1-80305617.inte...
# Users        ptc-user
# Query_time distribution
#   1us
#  10us  #
# 100us  ####
#   1ms  ####
#  10ms  ################################################################
# 100ms  ######################
#    1s
#  10s+
# Tables
#    SHOW TABLE STATUS FROM `app` LIKE 'events'\G
#    SHOW CREATE TABLE `app`.`events`\G
# EXPLAIN /*!50100 PARTITIONS*/
SELECT image FROM `events` WHERE `id` = 3128\G

この出力から、SELECT image FROM events WHERE id = 3128に、最長で100[ms]単位を要していることが分かります。

更に、Slow Queryに関連して、カーネルパラメータやコネクション数の調整のような普段は触らない部分を知るきっかけになりました。

遅いエンドポイントの探し方

遅いエンドポイントを探すためには、kataribeが使えます。このツールは、httpサーバのログを解析するツールです。httpサーバのログはNginx等で出力できます。

このツールは、下記の通りtoml形式でフォーマットを指定することでアクセスログをまとめてくれます。

$ sudo cat /tmp/access.log | kataribe -f ./kataribe.toml
Top 20 Sort By Total
Count   Total     Mean   Stddev     Min   P50.0   P90.0   P95.0   P99.0     Max  2xx  3xx  4xx  5xx  TotalBytes   MinBytes  MeanBytes   MaxBytes  Request
  154  52.263   0.3394   0.4680   0.000   0.245   0.261   0.513   2.890   3.148   11    0  143    0       10662         31         69        399  POST /api/events HTTP/1.1
  145  34.146   0.2355   0.0536   0.001   0.242   0.256   0.261   0.440   0.500    0    0  145    0        6920         31         47         55  PUT /api/events/1 HTTP/1.1
    1  24.126  24.1260   0.0000  24.126  24.126  24.126  24.126  24.126  24.126    1    0    0    0        4718       4718       4718       4718  GET /api/events?limit=12&offset=889 HTTP/1.1
    1  23.876  23.8760   0.0000  23.876  23.876  23.876  23.876  23.876  23.876    1    0    0    0        4791       4791       4791       4791  GET /api/events?limit=12&offset=1028 HTTP/1.1
    1  23.483  23.4830   0.0000  23.483  23.483  23.483  23.483  23.483  23.483    1    0    0    0        4492       4492       4492       4492  GET /api/events?limit=12&offset=341 HTTP/1.1
    1  23.412  23.4120   0.0000  23.412  23.412  23.412  23.412  23.412  23.412    1    0    0    0        4628       4628       4628       4628  GET /api/events?limit=12&offset=46 HTTP/1.1
    1  23.408  23.4080   0.0000  23.408  23.408  23.408  23.408  23.408  23.408    1    0    0    0        4412       4412       4412       4412  GET /api/events?limit=12&offset=1584 HTTP/1.1
   37  23.210   0.6273   0.6186   0.001   0.270   1.770   2.035   2.398   2.398   27    0   10    0         291          0          7         35  POST /api/logout HTTP/1.1
(snip)

このログから、GET /api/events?limit=12&offset=889 HTTP/1.1は1回しか呼ばれていないのに、24[ms]もかかっているので、ボトルネックになっている可能性があるなどのことが分かります。

このツールは、出力のどの部分を見れば良いのかよく分からないツールでした。実際に何度か使うと使い方がわかってくると思いますが、最初はこれを見ても見方や使い方がよく分かりませんでした。参考になる資料などありましたら教えていただきたいです。

N+1クエリ

N+1クエリとは、一覧の取得に1回、関連データの取得にN回、計N+1回のクエリを発行する際に起きる性能問題のことです。 これは、先述したkataribeのログを見て、極端に遅いエンドポイントのハンドラに含まれていることが多いです。

N+1クエリとは、下記のようなものです。なお、長くなるのでエラーハンドリングは割愛しています。脳内補完してください。

type Item struct {
  Name     string
  UserName string
}

var items []Item{}

itemRows, _ := dbc.Query("SELECT user_id, name FROM items")
defer itemRows.Close()
for itemRows.Next() {
  // idとnameをmap
  var userID int
  var item Item
  itemRows.Scan(&userID, &item.Name);
  
  userRows, _ =  dbc.Query("SELECT username FROM users WHERE id=?", userID)
  for userRows.Next() {
    // UserNameをmap
    userRows.Scan(&item.UserName)
    items = append(items, resItem)
  }
}
  

ここで、最初のクエリでitemsテーブルから得たrow1行ごとに、usersテーブルにクエリを実行していることが分かります。 このようにrow1行に対して別テーブルに呼び出しを行うことで、テーブルのレコード数が大きければ大きいほど実行時間が長くなるクエリがN+1クエリです。

そして、その改善方法のひとつとして、SQLの問い合わせは全部SQLに任せる、というものがあります。 今回の例でいえば、下記のようにテーブルへの問い合わせを別々に行わず、まとめて問い合わせることになります。

var items []Item{}

itemRows, _ := dbc.Query("SELECT u.name, i.name FROM items i INNER JOIN users u ON i.user_id=i.id")
defer itemRows.Close()
for itemRows.Next() {
  // UserNameとnameをmap
   var item Item
  itemRows.Scan(&item.UserName, &item.Name);
  
  userRows, _ =  dbc.Query("SELECT username FROM users WHERE id=?", userID)
  for userRows.Next() {
    // UserNameをmap
    userRows.Scan(&item.UserName)
    items = append(items, resItem)
  }
}

このように、INNER JOIN句を用いて内部結合を行うことで、各テーブルへの問い合わせをソースコードを介さずに行うことが出来ます。 そのため、直感的に早くなりそうだということが分かるはずです。

今回のイベントでの反省点

一言でいえば、変更点をメモしていなかったことです。 最初にも書いた通り、ちょこちょこ変更を加えている間にベンチに失敗するようになってしまいました。 小さな変更を加えるごとにベンチを回すのが理想だったのかもしれませんが、ベンチが回りきるまでに3分程度かかるので新しい機能の追加に応じてベンチを回すようにしていました。 その結果、新しい機能を追加したらベンチが正常終了しなくなり、元の状態に復帰できなくなってしまいました。

当初は、変更点くらいならGitのコミットで管理すれば良いかなと考えていたのですが、このイベントではソースコードのみならずDBやインフラのように複数変更する部分があります。 Git上では、stable version(1270)というコミットがあったのですが、このコミットにロールバックしてもインフラやDBの状態までは復元できませんでした :cry: 。 そのため、Gitのコミットのみでなく、何時ごろに何をしたかをざっくりとメモしておけばよかったと考えています。もっといい方法があれば教えていただきたいです。

ベンチマーカについて

今回のベンチマーカは、PythonTevernを使っていたようです。 しかし、テストに失敗したときにそれっぽいログは出力されていましたが、あまり見たことのないフォーマットだったのでどの部分が原因で落ちたのかが分かりにくかったです。 例えば、logoutの機構に関して、ブラウザでは正しく実行できているのにローカルでのベンチでは落ちているというケースも散見されました。 本番中は焦っていたので、ExpectedとAcctualの部分からソースコードのどこがおかしいかをざっくり探していたのですが、今思うと危なかったかな、とも思います。

おわりに

こういうイベント系は、参加記を書くまでがイベントな感じがするので参加記を書きました。 イベント全体を通して、6時間半という長丁場でしたが、ドキュメントや初期設定等に1時間、想定していた3台構成にするまでに1時間、ベンチを回している時間をまとめると30分程度、椅子を温めている時間が1時間と、効果的に手を動かせていたのは1時間程度と、濃い時間だったように思います。とはいえ、その間もソースコードを見ながら雑にインデックスを貼ったくらいで、ドキュメントの情報は殆ど使えていなかったように思います。 その点で言えば、まだまだISUCON初心者だと思うので、また別の機会があればこういったイベントに参加してみたいです。

明日はISUCON10の予選ですが、私は参加権が得られなかったので次回のISUCON11や他のチューニング系イベントで再チャレンジしたいと考えています。 ISUCON11では誰か一緒に出てほしいです。お茶くみくらいならやります。

最後になりますが、運営メンバーの方々、面白いイベントを開催していただき、ありがとうございました。 またこのイベントを開催する可能性があるらしいので、興味のある方は参加してみてはいかがでしょうか。

参考にした資料

SBテクノロジーでSOCアナリストとして2か月の就業型インターンをしてきた話

もくじ

はじめに

2月から2ヵ月にわたり、SBテクノロジーのMSS(Managed Security Service)に携わるSOCアナリストとして就業型インターンをしてきました。 このエントリは、節目としてエントリを残しておきたいという思いと、今後SBテクノロジーインターンを志望する方の参考になればという思いで書かれました。

SBテクノロジーis何?

一言でいうと、ICTサービス事業としてクラウドに特化して環境および運用を提供する会社です。事業内容はこちらをご覧ください

クラウドの環境および運用を提供するということは、そのセキュリティ監視も当然存在します。その一部に、私が従事した、SOCとして24時間365日セキュリティ監視を行うMSSがあります。

SBテクノロジーインターンに応募した理由

SOCとして従事するMSSに応募した理由は、以下の3点でした。

  • クラウドのセキュリティに特化しているという部分に興味を持ったため
  • 実業務としてSOCの経験をできるインターンが無かったため
  • 自分のセキュリティに関する知識・経験がどれだけ通用するのか知りたかったため

そもそも、SOCはその存在自体が隠蔽される性質を持つので、SOCのインターンは非常に珍しく感じました。それに加え、セキュリティ・キャンプで「クラウドのセキュリティに特化しています」と紹介していたため、「クラウドのセキュリティとは:thinking_face:」と気になっていたこともきっかけの1つです。

以上の理由から、将来SOCになるか否かは別として、SOCがどのような場所であり、そこで私に何ができるのかを知るためにインターンに応募しました。

SBテクノロジーでしたこと

定常業務

SOCの業務の中で私に課されたのは下記の3つの定常業務でした。

  • お客様の製品が正しく運用されているかの監視
  • 脆弱性情報の配信
  • お客様へのログから読み取れる攻撃傾向のご報告

これらの定常業務は、全ての業務を含めて3・4時間もあれば終わってしまうので、業務時間中に手があくこともしばしばありました。そのような時には、レポートのダブルチェックを行ったり、システム構成に関する会議に同席したりすることもありました。

半自動化による業務改善

それでも時間は余ってしまうもので、業務改善として「お客様の製品が正しく運用されているかの監視」を半自動化しました。半自動化に際して、アナリストのご意見をお聞きしながら試行錯誤を重ね、最終的に業務用ツールとして認められたので達成感がありました。

その過程で、「技術にこだわった開発」ではなく「需要に応じた開発」ができました。

趣味の開発では、面白そうならとりあえずインストールして動かしていましたが、実務ではリスクを増やさないために許可なく新しいソフトウェアを入れることは禁止されていました。

特に印象的だったのは、「このツールを入れたいんですが、良いですか?」とお聞きしたときの「そのツールを入れると、どんなメリットとリスクがあるの?」という返しです。業務に従事している方からすると当然なのかもしれませんが、こういった発想に至らなかった部分が私の未熟な部分なのだと感じました。

結果として、既に業務用ツールの一部で使用されていたPythonを選定することになったのですが、私はPythonを積極的に使ったことはなかったため、開発の過程でDocstringやclassにおけるgetter、setterの記法といったPythonの独特な表現を知ることができました。

また、下記のように、アナリストの方にツールを見せて意見をお聞きして、改善できたのも良かったです。

ア「初めて使う人にも分かりやすいように手順書があると良いかもね」 私『画像やQ&A付きの手順書制作しました!』

ア「Pythonがインストールできない時はどうするの?」 私『pyinstallerでexe化しました!』 私『ついでにexe化および必要な依存パッケージインストールのためのバッチファイルの用意しました!』

このように、実際に使う人の意見を聞きながら制作する経験ができたのは本インターンの良い副作用でした。

おわりに

業務的に秘匿にするべき部分が多く、なんとも抽象的な記述が多くなってしまいましたが、(おそらく)要点は伝えられたと思います。

インターンは就業型のインターンなので、現職のアナリストの業務のうち対応できそうなものを業務として与えられます。しかし、SOCが扱うシステムは複雑であるため、いくらスキルがあろうとも対応方法を知るために幾ばくかの時間を要します。そのため、全ての業務が与えられる訳ではなく、自分から仕事を探せる人間でないとかなり暇になると思います。

一方で、SOCの実業務を経験して雰囲気を知ることができる良い機会であるとも言えます。冒頭にも書きましたが、SOCのインターンは少なく、それもSOC見学のように短期間で終わるものが多いと考えています。そんな中、SBテクノロジーでは長期に及びインターンを実施してくださるので、その経験を積みたい方にはオススメだと考えています。

以上が、本インターンのエントリになります。 最後になりますが、SOCアナリストとして様々なことを学ぶことができて感謝しています。ありがとうございました。

NTTコミュニケーションズ SREエンジニア体験 参加記

はじめに

2020年2月15日(土)にNTTコミュニケーションズにて開催された「SREエンジニア体験」に参加しました。

午前中にSREエンジニアに関する座学を, 午後にGCPを用いたハンズオンを行いました。

ここで, SREについてかなりの知見が得られたので, 参加記としてまとめることとしました。

午前 - 座学

SREとは

SRE(Site Realiability Engineering)とは, システムの信頼性を担保するエンジニアリングのことです。

SREは, 品質の維持かつ高速なリリースサイクルの実現を目的とします。具体的には, アプリケーション基盤の構築および運用やリリースプロセスの整備(共通化)が挙げられます。

SREについては, 以下の本がおすすめです。

SRE サイトリライアビリティエンジニアリング ―Googleの信頼性を支えるエンジニアリングチーム

用語: SLI, SLO, SLAについて

SLOのすすめによると, それぞれの要素について以下のように書かれています。

SLI(Service Level Indicator)とは, サービスレベルの指標のことです。具体的には「リクエストの成功率」や「月間利用可能率」などが挙げられます。

SLO(Service Level Objective)とは, サービスレベルの目標のことです。具体的には「各四半期中の全リクエストの成功率は99.99%以上とする」が挙げられます。
この値は, 定期的にクライアントにヒアリングをしながら定期的に見直す必要があります。例えば, 満足度を指標としている場合は, 満足度を計る狩野モデルを用いて, 満足度が低い場合はSLOを引き上げるといった変更が必要になります。

SLA(Service Level Agreement)とは, サービスレベルに関する合意のことです。具体的には「SLOが満たされなかった場合, 利用料の50%を返金する」といったものが挙げられます。

従来の運用

従来の運用では, Dev*1Ops*2がチームとして分離しています。この運用では, Devは新機能や修正のためにシステムの変更を望むのに対し, Opsはシステム安定のためにシステムの変更を望みません。

システムの変更は必要なので, レビュープロセスやリリースプロセスを厳格化することで障害を減らそうとします。この結果, システムの規模が大きくなればなるほど, 運用コストも比例して増加します

また, SLAに違反しないようサービスの停止を極端に嫌い, 過度に慎重になります。

以上の要因により, サービスのリリースが遅くなる訳です。

SREによる運用

SREによる運用では, システムの規模に比例して運用コストが増加しません。なぜなら, リリースに応じた正常性確認を自動化するためです。システムの規模に応じて時間がかかる理由は, 正常性確認を手動で行っているためなので, 自動化は運用コストの膨張防止に大きく貢献します。

そして, 障害発生を0にするのは誤りであると考え, 障害発生時には影響を最小化する運用を行います*3。そのため, SLAに基づいたサービス品質を目指して, 早急にシステム障害から復旧できる仕組みを実装します。

また, SLOに違反しない限りはサービス断を許容し, 余った時間*4で新機能のリリースといった挑戦的なことを行います。

SLO違反を防ぐ仕組み

余った時間を用いると言えど, SLOに違反しては問題です。
そこで, SLO違反を検知する機構としてCanary Releaseが適用できます。

Canary Releaseとは, 新しいバージョンに流すトラフィックを制限することで, 新しいバージョンに問題があっても全体のSLOを担保するリリース手法です。例えば, 新しいバージョンに流すトラフィックを全体のトラフィックの1%に留めれば, 新しいバージョンが100%機能しなくとも全体へのダメージは1%を超えることはありません。

Canary Releaseには, OSSツールのIstioが利用できます。

CI/CD

CI(Continuous Integration)およびCD(Continuous Delivery)のことです。

CI(Continuous Integration)

リリースまでのパイプラインで言えば, Test/Buildの自動化がCIに該当します。

CIによる恩恵は, 品質の保証と開発者への情報提供です。 前者は, deployされたコードがtestを通っていることが保証されているので, 最低限の品質は保証されているという意味です。後者は, エンジニアがpushしたコードのフィードバックを即時にもらえるという意味です。

具体的なワークフローとしては以下の3つが挙げられます。

  • Run test localy
    • ローカル環境で動かないものは基本的にpushしないという発想
  • Build Servers
    • pushをトリガーとしてサーバ側でテストを含めた自動化が為されていること
  • Quality Control
    • ソフトウェア品質を高めることを目的とした性能測定および静的解析
    • i.e.) Lint toolやStyle Guide, 脆弱性情報を用いたコードの解析

CIのツール(ドキュメント)は以下の通りです。

CD(Continuous Delivery)

リリースまでのパイプラインで言えば, Deploy, Test*5, Approveの自動化がCDに該当します。

CDによる恩恵は, ソフトウェアがいつでもリリースできる状態にあることです。CDでは, deployおよびtest等をまとめて行うため, リリースできる状態が保証されます。

CDのdeployには以下の通り, deploy strategyという手法を状況に応じて使い分けます。

  • Rolling update
    • 新しいバージョンをクラスタ内で部分的に追加し, 問題がなければ古いバージョンを順番にクラスタから消していく手法
  • A/B testing
    • Traficを2つのバージョンのユニットA/Bに分けて調査をする手法
    • 2つのバージョンの性能評価のために用いられる
  • Blue/Green Deployment
    • Dev用にtest traficを流すためのGreen Podと, User用にuser traficを流すBlue Podを用意
    • Green Podで問題なさそうなら, user trafficをGreen Podに流す手法
  • Canary Release
    • 現行サーバの他にBaselineという比較用の現行サーバとCanaryという新しいバージョンのサーバを用意
    • Routerによってtrafficの操作を行い, BaselineおよびCanaryに全体の数パーセント程度のtrafficを流して比較することで新しいバージョンの検証をする手法

CDのツール(ドキュメント)は以下の通りです。

午後 - ハンズオン

こちらのGoogleのチュートリアルを参考にした, 段階的にCI/CDを実現していく形式のハンズオンを行いました。

手動deploy

最初は, 以下の通り手動でdeployしました。

  1. アプリケーションのコンテナ化
    • コンテナのimageIDを取得
  2. 作成したcontainer imageをcontainer repositoryにpush
    • docker imageIDとrepository nameを紐づけ
    • repository nameでpush
  3. kebernetesにmanifestを適用
    • manifestであるyamlファイルを編集して適用

CI/CDパイプラインの構築

手順としては少ないですが, 複数のdeployをする場合は大変ですし, ヒューマンエラーでミスをする可能性もあります。そこで, CI/CDパイプラインを構築することとしました。

変更点は以下の通りです。

  • アプリケーションのコンテナ化およびcontainer repositoryへのpushの自動化(CI)
    • appリポジトリへのpushを起点とするGCP Cloud Buildのトリガを設定
    • 設定用のyamlファイルはこちら
    • yamlファイルでは, docker buildおよびpushをする設定が書かれています
  • アプリケーションdeployの自動化(CD)
    • envリポジトリへのpushを起点とするGCP Cloud Buildのトリガを設定
    • 設定用のyamlファイルはこちら
    • yamlファイルには, kubernetesへのmanifestの適用およびproductionブランチへのpushをする設定が書かれています

ここで, envリポジトリにcandidateブランチおよびproductionブランチがあることが分かります。これらのブランチを分けている理由は, 昔のmanifestを適用することで簡単にGKE上で過去の状態を復元できるようにするためです。

各ブランチの役割は以下の通りです

  • candidateブランチ
    • これから本番環境にdeployするためのコードを保存
    • 最新版を本番環境にdeployするときはこちらを変更
  • productionブランチ
    • 本番環境にdeployされたコードを保存 − candidateブランチが本番環境に適応された段階でcandidateブランチからproductionブランチにpushされる

ログのモニタリング環境構築

自動化したはいいものの, アプリケーションの挙動を見守る必要があるはずです。そこで, ログをモニタリングするための機構を構築します。

ログのモニタリング時には, 以下の4つのシグナルを意識すると良いそうです。

  • リソース(Resources)
  • 使用率(Utilization)
    • リソースが処理中でbusyだった時間の平均
  • 飽和状態(Saturation)
    • リソースにサービスできない余分な作業がある度合い(処理できてないものは大抵キューに入れられいる)
  • エラー(Errors)
    • エラーイベントの数

そして, モニタリングを実現するには, PrometheusおよびGrafanaを用います。それぞれの役割は以下の通りです。

  • Prometheus
    • システムの監視や警告を行う
  • Grafana

また, 変更点は以下の通りです。

おわりに

CI/CDの構築を実践しながら学べるイベントでした。 GCPのサービスは課金が怖くてあまり触っていませんでしたが, これを機に無料枠で少しずつ触ってみようと思えました。 CI/CDを組むようなサービスを構築することは少ないですが, アーキテクチャを学べたのは良かったです。

来年もあればぜひ参加してみてはいかがでしょうか? (これ毎回言ってる気がしますね)

*1:開発者のこと

*2:運用者のこと

*3:経験上どう頑張ってもバグが消えないことは分かっているはずです。

*4:Error Budgetという

*5:こちらはDeployされたものを対象としたTest

NTTコミュニケーションズ セキュリティ脅威分析体験ハンズオン 参加記録

はじめに

2020年2月8日(土)にNTTコミュニケーションズ本社で開催されたTech Workshop「セキュリティ脅威分析体験ハンズオン」に参加しました。

午前中に脅威のシグネチャを検知するためのルールの書き方を, 午後にEDRのログの調査方法を, 実践を交えて学びました。午後の終盤には, 調査したログに対するインシデント報告書を作成して発表しました。

結果として, 「良かった発表」の得票数1位になり, 以下の書籍およびノベルティをいただきました。

午前 - Splunkチャレンジ

一言でいえば脅威のシグネチャを検知するためのルールの書き方を学びました。 具体的にはSplunkで管理しているプロキシログの中から脅威を見つけるためのルールの書き方と, IDSに設定するシグネチャであるSnortシグネチャの書き方を学びました。

参考までに, Splunkのクイックリファレンスガイドのリンクを貼っておきます。

前者も後者もルールを書くまでに3段階の工程を踏みます。 まず, 悪性通信を観察して共通点を見出します。 次に, その共通点をルールとして書いて悪性通信のみの集合に適用し, 全ての悪性通信が検知できていることを確認します。 最後に, そのルールを全体のログに対しても適用して悪性通信の検知を行います。

今回のハンズオンで用いられたログについても同様に, 悪性通信の共通点を見つけ, それをルール化しました。 SplunkとSnortシグネチャで書くクエリは異なりますが, 結局は悪性と考えられる通信の特徴をどれだけ高精度に見つけられるかが重要であると感じました。

私は単純に検知できるルールを書くので精一杯でしたが, 前に座っていた方がルールの精度にこだわっていたので, まだまだだと感じました。

午後 - EDRログ分析

EDR(Endpoint Detection and Response)とは, 脅威の検知とインシデントレスポンスのための記録を行う製品のことです。 EDRは問題が起きる前の予防策として用いるAV(Anti-Virus)と異なり, 脅威の検知やエンドポイントの隔離といった問題が起きた後の対応策のために用いられます。

ハンズオンでは, EDR製品で取得したログを用いてエンドポイントのログをどのように分析するかを学びました。

エンドポイントログ分析には, FireEyeのRedLineを用いました。このアプリケーションは, ログからProcess EventsやRegistry Key Eventsを一覧で表示して, その一覧に対して検索クエリを投げることが出来ます。また, 各ログについて,プロセス番号や親プロセス番号を取得することが出来ます。これらの機能を用いて, 怪しいプロセスを起点に調査範囲を広げていくことが重要になります。更に, ファイルごとのmd5ハッシュも表示されるため, Virus TotalやHybrid Analysisにハッシュ値を投げて既存の解析結果を得ることも重要です。

最終的に調査した内容を発表しました。冒頭にも書いた通り全体の中で最も良い評価を得られたのは, 対策案としてバッチファイルを書いたことだと考えています。これはひとえに大和セキュリティの忍者チャレンジのおかげなので, あのイベントには頭が上がりません。

また, 最後にスタッフの方から模範解答が公表されたので, 意識不足だった部分を3つ書いておきます。
1つ目は, そのログがどのファイルやプロセスから実行されたかを示す証跡を書くことです。EDR製品のログだけでは情報不足の場合もありますが, これを書くことで何がトリガとなって実行されたのかが明確になるため, 重要です。
2つ目は, 感染した端末の初期化を行うことです。いくら解析しようとも, 見落としが100%無いとは言い切れません。そのため, 基本的に感染したPCは初期化するのがベストです。
3つ目は, 同じネットワーク上の他のPCを調査することです。EDRのログで検知されていない他のPCでも感染している可能性があるため, 同じネットワーク上の他のPCも調査すべきです。

以上3点が頭から抜け落ちていたので, 念のため記載しておきます。

おわりに

今回の内容の殆どが未経験だったため, 良い経験になりました。
特に, EDRセキュリティは言葉だけ知っている程度だったため, 実際に手を動かしながら学べたのが非常にありがたかったです。スタッフの皆様, 本当にありがとうございました。

今後も同様のイベントを行うかもしれないので, その際はぜひ参加してみてはいかがでしょうか。

2019年の振り返りと2020年の目標を述べてみる

はじめに

 こんにちは。task4233と申します。

 この記事は, 私の2019年の振り返りと2020年の目標を述べることを目的として書かれました。
 記事全体は長く, 以下に目次があるので, 面白そうなもの, 興味のあるものだけ読むのも良いかもしれません。

もくじ

2019年の振り返り

 今年の目標は以下の6つでした。

 以上のうち, チェック([x])がついているものを達成しました。

将来について

 私は分野を絞ることがなく, 最終的にフルスタックなエンジニアを目指しています

 そのために, サイバーセキュリティ関連の仕事(解析やPentest)ができる仕事を志望しています(以下, サイバーセキュリティをセキュリティと略します)。

 理由は3つあります。
 1つ目は, セキュリティ・キャンプでセキュリティ沼の一部を知れたためです。
セキュリティ関連の知識を得ることは簡単ですが, それを実践することは非常に難しいです。例えば, 「XSSとは何ですか?」と聞いた時に答えられる人は多いでしょう。しかし, 簡単な脆弱性を含むサイトを提示して「このサイトでXSSを実践してください」というと手が止まる人が多いはずです。こういった実践的な知識は, 1人で得ようとすると非常に時間がかかります。そのため, 人から聞いたりハンズオンを受けたりして得ると非常に効率が良く, その点でセキュリティ・キャンプは非常に良い機会になりました。来年も応募できるので, セキュリティ関連に興味のある方は目を通しておくと良いかも知れません。

www.ipa.go.jp

 2つ目は, 今まで幅広く学んできたCS*1の知識が役に立つと考えているためです。
今までは, どの分野に興味を抱いても対処できるように幅広く学んできました。セキュリティ関連の仕事は, 専門知識がメインですが, 全体的に理解しておくべきな基本的なCSの知識が広いと感じています。例えば, DNS Rebindingという手法を理解して説明するためには, DNSのリソースレコードの存在やネットワーク上の通信等の知識を知っていることが前提条件です。今までの学習経験上セキュリティ関連の技術にはこういったことが多く, 今まで学習してきたことが効果的に使えると考えています。

 3つ目は, 5年・10年先ではセキュリティを意識できるエンジニアが一般的になると考えているためです。
文部科学省によると来年度(2020年度)から小学校のプログラミング教育が必修化されます。これにより, 小学生の頃から, プログラミングという概念を知る機会が極端に増えることが予想されます。そして, 若いうちからプログラミングを主体的に学ぶ子供が増えることが予想されます。その結果, 5年・10年先では, プログラミングが一般化して, 所謂ただ書けるだけのエンジニアは消えゆく運命だと考えています。そのため, セキュリティのことを意識したエンジニアに需要が生まれると考えています。一方で, 過去の脆弱なコードを学習して, Linterがunsecureなコードを検知する機械学習のシステムができる未来がくれば話は別ですが, それらは過去のデータにある事象しか対処できないはずです。我々人間は考えることができる生き物なので, 実際の現場で起きている問題を解消する場面では引けを取らないと考えています*2

 以上が, 私がサイバーセキュリティ関連の仕事(解析やPentest)ができる仕事を志望している理由です。おそらく, これらの仕事も, 将来は幅が広がって解析後にシステムを改修する部分も仕事内容に入っていくのではないかと考えています*3。そのため, 結果としてフルスタックエンジニアになれると考えています。

競技プログラミングを休止したことについて

 はっきりいうと, 私は競技プログラミングを休止しました 。 元から趣味なので, 休止というのは間違っているかもしれません。なぜなら, 私がセキュリティ関連(特に解析やPentest)を突き詰めたくなったためです。

 それぞれのジャンルで得られるものと失うものは, 以下の通りであると考えています。

  • 競技プログラミング
    • 得られるもの
      • コードを書けるという自信
      • アルゴリズムとデータ構造を理解して使う力
      • 他人のソースコードを読んで理解して使う力
      • 他の競プロerとの繋がり
      • 競プロ以外のジャンルを知る機会
    • 失うもの
  • セキュリティ関連
    • 得られるもの
      • セキュリティの専門知識を理解して使う力
      • 低レイヤを意識するようになる傾向
      • documentationから情報を得て使う力
      • セキュリティ界隈の繋がり
    • 失うもの
      • 時間

 こうして見ると, それぞれで得られることと私が将来したいこととのベクトルの向きが違うことが分かります。それぞれに良し悪しがあるのは事実です。しかし, 私の目的(今したいこと)に近いのは競プロではない, と断言できます。そのため, 私は競プロを休止しています。

 競プロを休止したことでコミュニティから離れざるを得ないのは辛いところです。しかし, 来年は多忙な日々から少しは離れられるので, 息抜きの趣味として続けて行くことができそうです。

記事を書くことについて

 言語化することで学んだ知識を自分の中でまとめ, 他人に共有できるという一石二鳥のメリットがあるため, 記事を書くようにしていました。

 実際に, 大きなイベントに参加した後は学んだ内容を参加記録としてhatenablogで投稿しています。また, メモ書き程度の内容は自分のTechBlogで投稿しています。 結果として1ヶ月に1記事程度のペースで投稿することができました

 これらの記事がどの程度の方の力になれたのかはわかりませんが, 少しでも力になれたのならば私は嬉しいです。

 また, 記事を書くために時間を要しますが, 後々見返して役に立つことがあるので未来の自分への投資として続けていきます。

ツイートで意識していたことについて

 「〜した」と「〜する」いう形式にこだわってツイートすることを意識していました。以前は, 「〜したい」や「〜になりたい」というツイートをしていたこともありましたが, 大抵やらないことが多かったので, それらのツイートは極力しないように心がけました。

 以上の意識によって, 何かを達成したり宣言したりすること以外でツイートをしないようになり, 以前よりも多くのことをするようになった気がします(気のせいかもしれません)。

 とはいえ, つまらないこともツイートしたくなることがあります。その際は, サブ垢のSubTaskerでツイートようにしています。なぜなら, 私の本垢をフォローしている方々に対して不毛なツイートを届けるのが嫌だからです。

 そのついでに, サブ垢でのツイートを全て英語にしています。これにより, つまらない内容を英語で話す力が身につくと考えています。実際はどうなのかわかりませんが, 英語ができないと二次的な情報しか得られなくなるので, 英語と仲良くなりたいという意味を込めて続けています。

最近のアニメに関する不毛なツイート

大学について

 基本的にマジメに出席していました*4。後に書きますが, 教職を続けているため色々と忙しく, 今まで以上に自由な時間が作れない日々が続いていました。今年の前期まで週6, 1限から遅い時は6限まで大学に行っていました。教職を続けている割には, 今までの平均GPAは4.0ですし頑張ってきたと自負しています。

 そして, 大学の関係で夏の7月下旬から8月中旬までカリフォルニア州立大学にてPBL*5に参加することができました。そこでは, FPGAを使いAD/DAコンバータを制御する経験ができました。その結果, AD/DAコンバータのデータシートの見方やVerilogの書き方, オシロスコープの使い方等を学ぶことができました。

FPGAでDAコンバータを制御して鋸波をオシロスコープで表示している様子

 趣味でアプリケーション開発をした経験はありましたが, ハードウェアに関する知識は皆無だったため, 非常に良い経験ができたと考えています。

 これは余談ですが, サンフランシスコに立ち寄った際にIntel本社やAppleのInfinite loop, GitHub本社, Unity Technologies, Amazon Goに立ち寄ることが出来たのも非常に良かったです。

Intel Headquarters

Apple infinite loop

GitHub Headquarters

Unity Technologies

 また, 大学でのグループ開発も非常に良い経験になりました。具体的には, 「ウォーターフォール型で開発しろ」と言われたのにアジャイル開発をしたり, docker-composeやnginxといった技術に触れたりしました。PullRequestを投げて, reviewしあうのは新鮮で良い経験になりました。

 チーム内でいざこざが起きたこともありましたが, マネジメント役のチームリーダやお互いの対話によって解決できたので, チーム内のムード作りや対話の重要性を身を持って経験できました。今回のチームリーダはコードを殆ど書いていませんでしたが, チームメンバの面倒見がよく, 非常に良いリーダシップを持つ人でした。彼は本当に凄い人で, 自分のできる範囲内で出来ることをひたすらに頑張る人でした。また, 一緒に開発してくれたメンバーも最後までめげずに開発し, 最終発表に間に合わせてくれたことには本当に感謝していますし, チームメンバに恵まれていたと強く感じています*6

 大学でマジメに学び続けられた理由としては, 大学の居心地の良さもありますが, 大学の友人と話ができる楽しさが大きいです。イベントでお会いした方はわかると思いますが, 私は話をして相手のことを知ったり, 新しい情報を得たりすることが好きです。教職を続けてできた友人もいますし, 大学では充実した生活を続けられてきたと思います。

 一方で, 無駄な講義もあったと考えています。正直, 今までの経験を通して, 私は大学の講義は必要最低限で良いと考えています。なぜなら, 資料を見ればわかるようなことを蹉跎歳月するのはもったいないと考えているためです。この点に関しては, GPAと将来の自分を天秤に掛けて慎重に検討すべき内容なので, 一概に正しい主張とは言えません。少なくとも, 過去の私にアドバイスできるならば, 大学の講義は必要最低限出るように説得します。

教職過程について

 私は教職課程を3年間続けてきました。

 今年は介護等体験ということで, 5日間のデイサービスでの実習体験と2日間の特別支援学校での実習体験をしました。これらの体験から, 相手に応じた対応の必要性を改めて学べました。この対応というのは, 相手に応じてレベルを下げるということではなく, 相手が受け取りやすいように工夫するということです。

 「同じでは?」と思うかもしれませんが, 前者には相手を侮蔑する意味が含まれていると考えています。例えば, お子ちゃま言葉を使ったり, 体が不自由な方の全ての手伝いを行ったりするといったことです。これらの行為は相手の尊厳を貶す行為と等しいです。そのため, ゆっくりと話したり, できる部分は本人にやらせたりする工夫をとるべきであると考えています。

 また, 教職を一緒に続けているメンバーとも気軽に話せるようになり, 続けていて良かったなと感じています。今まで辞めずに続けているということもあってか, 1人1人私にない尊敬できるスキルや特徴を持っています。その多くは処世術や明るさといったもので, 最近は彼らのおかげで割と明るく, ポジティブな思考を持てるようになったと感じています。

[deleted]

全落ちしたインターンの反省と対策について

 夏のインターンでは, 4社受けて全て落ちました。理由として, 実力不足や経験不足, コードのレベルが低いというフィードバックがありました。私は, 今まで教職課程や大学に全振りしていたので, 学問を真面目に修めて, 自分なりに挑戦してきたのに, こうなってしまったんだなというつらい気持ちになりました。また, 夏の前半を前述したカリフォルニア州立大学でのPBLと, セキュリティ・キャンプに費やしてしまったため, それも原因の1つだったと思います。

 友人には8社もインターンに行ったと言われ強い劣等感を感じていました。しかし, 前述したセキュリティ・キャンプで他の参加者から「taskさんは色々やってきた過去があるんだからもっと自信持って良いと思うよ」と言われ, その劣等感を払拭できたのを良く覚えています。

 全てのインターンに落ちた原因は, スケジューリングの下手さによるものだと考えています。大学が忙しすぎたということもありますが, 微妙な実力で装備を入念に整えなかったのは大きな失態であると考えています。そのため, 来年度以降にインターンを希望する方は, 当然かとは思いますが 早めに動くことをオススメします

 この経験を元に, 秋, 冬, 春のインターンではスケジューリングを意識して, 早めに行動するように心がけました。結果として, 既に1社のインターンが確定しており, 他の会社のインターンも検討している状態です。

徹夜は身を滅ぼすということについて

 最後に, 戒めの意も込めて, 1つ言いたいことがあります。徹夜はやめるべきです。こんな時間にブログを書いている身なので説得力は低いですが, 徹夜は本当に良くないです。次の日を棒に振り, その余波は週末まで響きます。結果として, 週末に十分に作業ができなくなり, 最終的には月末, 年末へと被害が広がります。

 徹夜をしないために, 高効率化をするのが良いと考えています。具体的には, Ciruelaさんのブログで書かれていたので, 引用させていただきます。

高効率化 夜に頭が働かない場合は寝る. CTFでは手が止まったら諦める. 飽きたら別のことをする. 面倒ごとは真っ先に撃破する.

2020年作戦立案 - Battle for my life

 今年度以降, 意識していきます。

2020年の目標

  • SecHack365に応募する
  • セキュリティ・キャンプ全国大会2019のチューターに応募する
  • 高度情報技術者試験かセキュリティ資格試験AC
  • CTFの本番で正答率10~20%の問題を通す
  • セキュリティに関するOSSツール(拡張)を作る

 以上が, 私の将来像を考えた時に来年達成しておく必要がある5つの目標です。
 昨年は深く考えずに目標を立ててしまったので, 今年は将来を意識した目標を立てました。

おわりに

 以上が, 2019年の振り返りと2020年の目標でした。

 2019年の目標は達成できなかったものもありますが, 将来像と合致していなかったので納得しています。
 2019年当初はあまり力がなかったですが, 1年間を振り返ると何かとアクティブに動けて, 自分の実力を再確認して, 将来を考えられる良い年だったと感じています。未だに「これはできる」という1つの分野, スキルがないため, 来年は研究をしながらその分野やスキルを作るつもりです。

 長くなってしまいましたが, ここまでお読みいただきありがとうございました。
 少しでもためになる部分があれば幸いです。
 では, 今年も1年よろしくお願いします。

*1:Conputer Science

*2:自己学習型の機械学習システムが確立されればどうしようもありませんが, その際は他の業界も潰れていると思うので, これがベストだと考えています

*3:既に行われているかもしれませんが, 情報不足なのでよくわかりません

*4:これは自分自身に対する皮肉の意を込めています

*5:Project Based Learning

*6:前日はほぼ全員徹夜していた気がします

C言語のポインタとアドレスをアセンブリコードを用いて理解してみる

本記事はデジクリアドベントカレンダー2019の12/17用に書かれました。

TL;DR

  • 「アドレス」はデータが保存される場所
  • 「ポインタ」はアドレスを格納するための領域
  • ポインタ宣言はアドレスを格納するためのポインタ変数を宣言すること
  • 参照外しはポインタに格納されているアドレス先のデータを, 先頭アドレスと型情報を元に参照すること
  • アドレスを調整することで他の変数にもアクセスできる

もくじ

目標

本記事の目標は以下の3点です。

  1. 「アドレス」と「ポインタ」が何を表しているかを理解する
  2. ポインタ宣言と参照外しの違いを理解する
  3. キャストと参照外しを用いて別の変数にアクセスする

なお, タイトルにもある通り, 本記事ではC言語およびアセンブリコードを用います。なぜなら, アセンブリコードを読むことで実行時に何が起きているかが直感的に理解できるためです。

なお, アセンブリについては以下の記事を読むことを強くオススメします。 基礎知識やアセンブリの読み方, メモリやレジスタの仕組み等が詳しく書かれており, 非常にわかりやすいです。

qiita.com

1. 「アドレス」と「ポインタ」が何を表しているかを理解する

アドレス

データが保存される場所のことです。正しくは「メモリアドレス」といいます(長いので以後アドレスとします)。値を変数に格納したり, 正しく関数を呼び出したりできるのは, このアドレスがあるためです。

アドレスは1バイト単位でランダムアクセスすることが可能です。1バイトは一般に8ビットで, 10進数で0から255の値を格納することができます。それ以上の数を格納したい場合は, 複数のバイトをまとめて使用することで実現します。

ビットとバイトの違いが分からない方はこちらの記事が参考になるかもしれません。

さて, C言語ではこのアドレスを&変数名のように, &演算子(アドレス演算子) を用いて表現します。例えば, 変数aのアドレスが欲しい時は&aのように書くことで, その変数が格納されている先頭アドレスを取得できます。先述した通り, データが大きい時は複数のバイトに連なってデータが格納されるため, 連続しているうちの先頭のアドレスを変数のアドレスとしています

では, C言語のコード例を見てみましょう。 コードと実行結果はこちらから確認できます。

#include <stdio.h>

int main() {
  int num = 57;
  // 変数の値を表示
  printf("変数numの値は%d\n", num);
  // 変数のアドレスを表示
  printf("変数numのアドレスは%p\n", &num);
  return 0;
}

// 出力
// 変数numの値は57
// 変数numのアドレスは0x7ffd8e972c5c

変数の値とアドレスが正しく出力されていますね。 printf()関数の引数として, 前者はnumを, 後者は&numを使うことで, 正しく値が取得できていることがわかります。

では, 先ほどのコードをコンパイルしたアセンブリコードを見てみましょう。 コードはこちらから確認できます。 それぞれのコードの意味はコメントで加えておきました。

.LC0:
        ;「変数numの値は%d\n.string "\345\244\211\346\225\260num\343\201\256\345\200\244\343\201\257%d\n"
.LC1:
        ; 「変数numのアドレスは%p\n」
        .string "\345\244\211\346\225\260num\343\201\256\343\202\242\343\203\211\343\203\254\343\202\271\343\201\257%p\n"
main:
        push    rbp                     ; ベースポインタをスタックに積む
        mov     rbp, rsp                ; rspをベースポインタの所に持ってくる
        sub     rsp, 16                 ; rspをスタックトップに持ってくる
        mov     DWORD PTR [rbp-4], 57   ; rbp-4のアドレスからDWORD PTR(4バイト分)の領域に57という値を格納
        mov     eax, DWORD PTR [rbp-4]  ; rbp-4のアドレスからDWORD PTR(4バイト分)の領域にある値をeaxに格納
        mov     esi, eax                ; esiにeaxの値を格納(printf関数の引数)
        mov     edi, OFFSET FLAT:.LC0   ; ediに「変数numの値は%d\n」という文字列を格納(printf関数の引数)
        mov     eax, 0                  ; eaxに0を格納
        call    printf                  ; printf関数を呼び出す
        lea     rax, [rbp-4]            ; raxにrbp-4のアドレスを格納
        mov     rsi, rax                ; rsiにraxの値を格納(printf関数の引数)
        mov     edi, OFFSET FLAT:.LC1   ; ediに「変数numのアドレスは%p\n」という文字列を格納(printf関数の引数)
        mov     eax, 0                  ; eaxに0を格納
        call    printf                  ; printf関数を呼び出す
        mov     eax, 0                  ; eaxに0を格納
        leave                           ; high-level procedure exit
        ret                             ; return

ここで注目していただきたいのは, 変数numのを表示する時と変数numのアドレスを表示する時に使ったアセンブリ命令の違いです。

前者では以下のようにmovを使用しています。

mov     eax, DWORD PTR [rbp-4]  ; rbp-4のアドレスからDWORD PTR(4バイト分)の領域にある値をeaxに格納

movはMOVeのことです。Intel SDMの5.1.1 Data Transfer Instructionsによると, 汎用レジスタ間でデータを移動すると書かれています。したがって, 変数の値そのものに働きかけをしていることがわかります。

一方, 後者では以下のようにleaを使用しています。

lea     rax, [rbp-4]            ; raxにrbp-4のアドレスを格納

leaはLoad Effective Addressの略で, 直訳すると実行アドレスのロードのことです。Intel SDMでは5.1.13 Miscellaneous Instructionsに書かれています。したがって, 変数の実行アドレスを取得するという働きかけをしていることがわかります。

以上より, C言語アセンブリに以下のような対応付けができます。 なお, Cでの変数名はnumで, [rbp-4]から格納されているとします。

変数の値 変数のアドレス
C言語 num &num
アセンブリ mov rax, DWORD PTR [rbp-4] lea rax, [rbp-4]

ポインタ

変数や関数が格納されているアドレスを格納するための領域です。先ほど, アドレスの取得の仕方を紹介しましたが, 一般的な変数にアドレスは格納しません。そこで, このポインタを使用します。

C言語では, ポインタを書く際に*変数名のように, *を用いて表現します。この変数名は自由に宣言できますが, 他の変数名と重複してはいけません。

難しく考えることは無いです。ポインタはアドレスを格納するための領域です。

では, C言語のコードを見ていきましょう。 コードと実行結果はこちらから確認できます。

#include <stdio.h>

int main() {
  int num = 57;
  // ポインタに変数numのアドレスを格納
  int* ptr = &num;

  printf("変数numのアドレスは%p\n", ptr);
  return 0;
}

// 出力
// 変数numのアドレスは0x7ffd5324a174

アドレスが正しく出力されていることがわかります。

では, 先ほどのコードをコンパイルしたアセンブリコードを見てみましょう。 コードはこちらから確認できます。 それぞれのコードの意味はコメントで加えておきました。

.LC0:
        ; 「変数numのアドレスは%p\n」
        .string "\345\244\211\346\225\260num\343\201\256\343\202\242\343\203\211\343\203\254\343\202\271\343\201\257%p\n"
main:
        push    rbp                     ; ベースポインタをスタックに積む
        mov     rbp, rsp                ; rspをベースポインタの所に持ってくる
        sub     rsp, 16                 ; rspをスタックトップにセットする
        mov     DWORD PTR [rbp-12], 57  ; rbp-12のアドレスからDWORD PTR(4バイト分)の領域に57という値を格納
        lea     rax, [rbp-12]           ; rbp-12のアドレスをraxに格納
        mov     QWORD PTR [rbp-8], rax  ; raxの値をrbp-8からQWORD PTR(8バイト分)の領域に格納
        mov     rax, QWORD PTR [rbp-8]  ; rbp-8からQWORD PTR(8バイト分)の領域の値をraxに格納
        mov     rsi, rax                ; raxの値をrsiに格納
        mov     edi, OFFSET FLAT:.LC0   ; ediに「変数numのアドレスは%p\n」という文字列を格納
        mov     eax, 0                  ; eaxに0を格納
        call    printf                  ; printfを呼び出し
        mov     eax, 0                  ; eaxに0を格納
        leave                           ; high-level procedure exit
        ret                             ; return

ここで注目していただきたいのは, 以下の通り変数のアドレスを格納する際にDWORD PTR(4バイト分)ではなく, QWORD PTR(8バイト分)の領域を用いていることです。

       lea     rax, [rbp-12]           ; rbp-12のアドレスをraxに格納
       mov     QWORD PTR [rbp-8], rax  ; raxの値をrbp-8からQWORD PTR(8バイト分)の領域に格納

これは当然といえば当然なのですが, x86-64は64ビットのOSなので, アドレスは64ビット(8バイト)です。そのため, アドレスを格納するためには8バイトの領域が必要になり, このような命令を実行しています。

以上より, C言語アセンブリに以下のような対応付けができます。 なお, C言語での変数名はnumで[rbp-4]から格納されているとします。 そして, C言語でのポインタ名はptrとします。

変数の値 変数のアドレス 変数のポインタ
C言語 num &num ptr
アセンブリ mov rax, DWORD PTR [rbp-4] lea rax, [rbp-4] mov QWORD PTR [rbp-8], rax

まとめると, 「アドレス」はデータが保存される場所のこと, 「ポインタ」はアドレスを格納するための領域です。

2. ポインタ宣言と参照外しの違いを理解する

新たに「ポインタ宣言」と「参照外し」という言葉が出てきました。ポインタわからんという方はこの2つを区別できていないのではないかと考えています。そのため, ここでそれぞれの内容を述べておきます。

「ポインタ宣言」は, アドレスを格納するため変数を宣言することです。つまり, 先ほどポインタの説明で出てきたポインタの使い方は「ポインタ宣言」にあたります。

一方, 「参照外し」はポインタに格納されているアドレス先のデータを参照することです。 実際のC言語のコード例を見てみましょう。

#include <stdio.h>

int main() {
  int num = 57;
  // ポインタに変数numのアドレスを格納
  int* ptr = &num;

  // 参照外しにより, ptrのアドレスの先の値を取得
  int referred_num = *ptr;
  printf("%d\n", referred_num);
  return 0;
}

// 出力
// 57

参照外しによりptrが保持しているアドレス先に格納されている値を取得できていることがわかります。

では, このコードをコンパイルしたアセンブリコードを見てみましょう。 コードはこちらから確認できます。

.LC0:
        .string "%d\n"
main:
        push    rbp                      ; ベースポインタをスタックに積む
        mov     rbp, rsp                 ; rspをベースポインタの所に持ってくる
        sub     rsp, 16                  ; rspをスタックトップにセットする
        mov     DWORD PTR [rbp-16], 57   ; rbp-16のアドレスからDWORD PTR(4バイト分)の領域を確保して, 57を格納する
        lea     rax, [rbp-16]            ; rbp-16のアドレスをraxに格納
        mov     QWORD PTR [rbp-8], rax   ; raxの値をrbp-8からQWORD PTR(8バイト分)の領域に格納
        mov     rax, QWORD PTR [rbp-8]   ; rbp-8からQWORD PTR(8バイト分)の領域の値をraxに格納
        mov     eax, DWORD PTR [rax]     ; raxに格納されているアドレスからDWORD PTR(8バイト分)の領域の値をeaxに格納
        mov     DWORD PTR [rbp-12], eax  ; rbp-12からDWORD PTR(4バイト分)の領域にeaxの値を格納
        mov     eax, DWORD PTR [rbp-12]  ; rbp-12からDWORD PTR(4バイト分)の領域にある値をeaxに格納
        mov     esi, eax                 ; esiにeaxの値を格納
        mov     edi, OFFSET FLAT:.LC0    ; ediに「%d\n」を格納
        mov     eax, 0                   ; eaxに0を格納
        call    printf                   ; printf関数を呼び出し
        mov     eax, 0                   ; eaxに0を格納
        leave                            ; high-level procedure exit
        ret                              ; return

今回の参照外しに該当する部分は以下になります。

        mov     eax, DWORD PTR [rax]     ; raxに格納されているアドレスからDWORD PTR(8バイト分)の領域の値をeaxに格納

アセンブリでは[ ]で囲むと, 囲まれた値をアドレスとして認識し, そのアドレス上の値にアクセスします。したがって, 参照外しが正しく行われていることがアセンブリでも確認できました。

さらに, この参照外しは複数回行うことが可能です。 以下のこちらのコードを見てみましょう。

#include <stdio.h>

int main() {
  int num = 57;
  int* ptr = &num;
  int** ptr2 = &ptr;
  int*** ptr3 = &ptr2;
  int**** ptr4 = &ptr3;
  int***** ptr5 = &ptr4;

  int referred_num = *****ptr5;
  printf("%d\n", referred_num);
  return 0;
}

// 出力
// 57

なんとも仰々しいですが, 正しく実行できます。 これもコンパイルされたアセンブリを読めば簡単に理解できます。

.LC0:
        .string "%d\n"
main:
        push    rbp                     ; ベースポインタをスタックに積む
        mov     rbp, rsp                ; rspをベースポインタに持ってくる
        sub     rsp, 48                 ; rspをスタックトップにセットする
        mov     DWORD PTR [rbp-16], 57  ; rbp-16のアドレスからDWORD PTR(4バイト分)の領域に57を格納する
        lea     rax, [rbp-16]           ; raxにrbp-16のアドレスを格納
        mov     QWORD PTR [rbp-24], rax ; rbp-24のアドレスからQWORD PTR(8バイト分)の領域にraxの値を格納する
        lea     rax, [rbp-24]           ; raxにrbp-16のアドレスを格納
        mov     QWORD PTR [rbp-32], rax ; rbp-32のアドレスからQWORD PTR(8バイト分)の領域にraxの値を格納する
        lea     rax, [rbp-32]           ; raxにrbp-32のアドレスを格納
        mov     QWORD PTR [rbp-40], rax ; rbp-40のアドレスからQWORD PTR(8バイト分)の領域にraxの値を格納する
        lea     rax, [rbp-40]           ; raxにrbp-40のアドレスを格納
        mov     QWORD PTR [rbp-48], rax ; rbp-48のアドレスからQWORD PTR(8バイト分)の領域にraxの値を格納する
        lea     rax, [rbp-48]           ; raxにrbp-48のアドレスを格納
        mov     QWORD PTR [rbp-8], rax  ; rbp-8のアドレスからQWORD PTR(8バイト分)の領域にraxの値を格納する
        mov     rax, QWORD PTR [rbp-8]  ; rbp-8のアドレス先の値をraxに格納する
        mov     rax, QWORD PTR [rax]    ; raxのアドレス先の値をraxに格納する
        mov     rax, QWORD PTR [rax]    ; raxのアドレス先の値をraxに格納する
        mov     rax, QWORD PTR [rax]    ; raxのアドレス先の値をraxに格納する
        mov     rax, QWORD PTR [rax]    ; raxのアドレス先の値をraxに格納する
        mov     eax, DWORD PTR [rax]    ; raxのアドレス先の値をeaxに格納する
        mov     DWORD PTR [rbp-12], eax ; eaxの値をrbp-12のアドレスからDWORD PTR(4バイト分)の領域に格納する
        mov     eax, DWORD PTR [rbp-12] ; rbp-12のアドレスからDWORD PTR(4バイト分)の領域にある値をeaxに格納する
        mov     esi, eax                ; esiにeaxの値を格納する
        mov     edi, OFFSET FLAT:.LC0   ; ediに"%d\n"を格納する
        mov     eax, 0                  ; eaxに0を格納する
        call    printf                  ; printfを呼び出す
        mov     eax, 0                  ; eaxに0を格納する
        leave                 ; high-level procedure exit
        ret                             ; return

これを見れば,

int referred_num = *****ptr5;

という部分で,

mov     rax, QWORD PTR [rax] 

が複数回繰り返されて参照外しが行われていることが分かるはずです。

以上より, 「ポインタ宣言」は, アドレスを格納するため変数を宣言することであり,「参照外し」はポインタに格納されているアドレス先のデータを参照することであるとわかります。

3. キャストと参照外しを用いて別の変数にアクセスする

また新しい単語「キャスト」が出てきました。
「キャスト」とは, 変数を何の型で解釈するか指定する操作です。
キャストは(型名)を先頭に付与することで実現できます。
例えば, numという変数をlong型にキャストしたい場合は, (long)numと書けば良いです。

こちらのコードを見てください。

#include <stdio.h>

int main() {
  long num = 1;
  int num2 = 2;
  char num3 = '3';

  long* ptr = &num;
  int* ptr2 = &num2;
  char* ptr3 = &num3;

  printf(" ptr : %p\n", ptr);
  printf(" ptr2: %p\n", ptr2);
  printf(" ptr3: %p\n", ptr3);

  char* ptr4 = (char*)(ptr - 1) + 3;

  printf(" ptr4: %p\n", ptr4);
  printf("*ptr4: %c\n", *ptr4);
  
  return 0;
}

// 出力
//  ptr : 0x7ffce2e87928
//  ptr2: 0x7ffce2e87924
//  ptr3: 0x7ffce2e87923
//  ptr4: 0x7ffce2e87923
// *ptr4: 3

このコードを実行すると, 以下のような結果が得られます。

このコードで注目していただきたいことは, 以下の通りptrを利用してptr3のデータにアクセスできているということです。

 char* ptr4 = (char*)(ptr - 1) + 3;

アクセスする方法は単純で, ptrからptr3のアドレスを取得しているだけです。しかし, これコードの意味を理解するには内部で何がおきているのかを理解する必要があるはずです。

そのために, コンパイル後のアセンブリコードを確認します。 ここで, 簡単のためにprintf()関数を取り除いています。

main:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-40], 1
        mov     DWORD PTR [rbp-44], 2
        mov     BYTE PTR [rbp-45], 51
        lea     rax, [rbp-40]
        mov     QWORD PTR [rbp-8], rax
        lea     rax, [rbp-44]
        mov     QWORD PTR [rbp-16], rax
        lea     rax, [rbp-45]
        mov     QWORD PTR [rbp-24], rax
        mov     rax, QWORD PTR [rbp-8]
        sub     rax, 8
        add     rax, 3
        mov     QWORD PTR [rbp-32], rax
        mov     eax, 0
        pop     rbp
        ret

このうち, 注目していただきたいのは以下の4行です。

        mov     rax, QWORD PTR [rbp-8]      ; rbp-8からQWORD PTR(8バイト分)の領域にある値をraxに格納
        sub     rax, 8                      ; raxから8引く
        add     rax, 3                      ; raxに3を足す
        mov     QWORD PTR [rbp-32], rax     ; rbp-32からQWORD PTR(8バイト分)の領域にraxの値を格納

わざわざとった値に対して加減算を行っています。 これは, 今回の変数が以下のように積まれているためです。

アドレス番号 補足
0x0000fff0 num long(8バイト分)
0x0000ffec num2 int(4バイト分)
0x0000ffeb num3 char(1バイト分)

したがって, numのアドレスを持つポインタに8引いて3を足せばnumからでもnum3にアクセスできるわけです。 C言語のコードでは1しか引いていないのに8引かれているのは, ptrがlong型のポインタであるためです。long型は8バイトなので, -1しただけで8バイト分のアドレスを戻っています。

したがって, 同様に以下の場合でも正しくアクセスできますね。

// char型のポインタとしてキャストした上で5バイト分戻る
char* ptr4 = ((char*)ptr - 5);

つまり, ポインタでの参照外しをする際には, 先頭アドレスによって情報を読み取っているということです。

仮に先頭アドレスが正しくとも, 型が誤っていると正しくデータを取得できません。そのため, 型付き言語では変なキャストをするとwarningが出る訳です。

したがって, 先ほどの参照外しの説明は不十分です。 参照外しはポインタに格納されているアドレス先のデータを, 先頭アドレスと型情報を元に参照することと言った方がより正しいでしょう。

おわりに

ここまでお読みいただきありがとうございました。 本当はCコンパイラでアドレスとポインタを自作してその上で動かしたかったのですが, 時間が間に合いませんでした(演算子の定義等までは実装できました)。

本記事は参照外しとポインタ宣言が混同している方のために書いたつもりなんですが, 結果的に怪文書ができてしまった気がします。

なにはともあれ, ここまでお読みいただきありがとうございました。 変な部分があればマサカリを投げてください。 ありがとうございました。

学生LT #31 参加記録

TL;DR

  • 公開枠18組の概要と感想をまとめた
  • 5分間のLTをしてきた
  • LTの準備が不十分だった

もくじ

はじめに

本日(2019/12/08)に開催された学生LT#31に参加したので, その感想と反省を書きます。

学生LT#31の詳細は, connpassページをご覧ください。

student-lt.connpass.com

ライブ映像は公開されています。

https://youtu.be/cX13Wwa0Mds

以後, LT資料, および動画のリンクを載せていますが, 抜けがある場合はご指摘ください。よろしくお願いします。

当日の流れ

遅刻しました。

後述する解析に熱中しすぎて, 家を出る時間を超えてしまいました。

LT

us pycon参加録 by sakakendo

LT資料

docs.google.com

動画

https://youtu.be/cX13Wwa0Mds?t=2100

概要

Pycon2019に自費(?)で参加してきた話でした。

感想

海外カンファレンスに行ってみたいので, どこかのScholarshipに選出されたいものです。今の所MerkariさんのStepupgoで頑張っているので, GoCon出られると良いなと思っています。

Npurse使ってECサイト構築した話 by らいう

LT資料

docs.google.com

動画

https://youtu.be/cX13Wwa0Mds?t=2908

概要

タイトル通り, Mpurseを使ってECサイトを作った話でした。Mqurseの特徴についても触れられていました。

感想

大体の処理をMqurseに投げられるのは良さそうです。しかし, そのサービスに脆弱性が生まれた場合にどうなるのかを考えると怖そうです。

Kotlinで作ったJVMで動くCLIツールをGraalVMのnative imageでmacOS向けにバイナリ吐いてみた by マヤミト

動画

https://youtu.be/cX13Wwa0Mds?t=3524

概要

GraaVM上で, Kotlinコードをnative imageにコンパイルした話でした。JVMとの比較もしており, それぞれのメリット/デメリットも紹介していました。

感想

JVMもそこそこの速さですが, それよりも高速な実行時間というのは魅力的でした。

軽率にレイトレする by にー兄さん

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=3524

概要

古典的レイトレーシング(Ray Tracing)のアルゴリズム, Rayの生成と交差判定, ShadowRayによる可視判定等の理論について紹介していました。

感想

3DCGって出来る人すごいですよね。
以前レイトレは少し触れていたので, 古典的レイトレならもう少しわかりやすく話せたのではないかなという気がしました。BRDFでいえば, 「物体表面の点においてある方向から入射した光が, ある方向へ出射する割合」と言った方がわかりやすいと思いました。しかし, 割合というと[0, 1]のrangeを超えてしまう時に対応できないので難しいところです。ここ, 詳しい人に教えていただきたいです。

ref: BRDF range -- greater than 0...1 ?

ArduinoできらきらフルカラーLEDテープを光らせる話 by たかぴろ

動画

https://youtu.be/cX13Wwa0Mds?t=5004

概要

ArduinoでLチカして終わってしまう人が多いです。そんな中, フルカラーLEDテープを光らせる(制御)していました。

感想

Arduinoに触ってみたこともありましたが, たかぴろさんがおっしゃっていたように, 実践できるほどまでやっていませんでした。一貫して続けられるものを見つけたいものです。

RustでWebassemblyインタプリタを作った話 by tkr

LT資料

gitpitch.com

動画

https://youtu.be/cX13Wwa0Mds?t=5453

Agenda

感想

wasmを触ったことがなかったですが, アセンブリに対する抵抗感が薄くなった今, 触ってみたいと思いました。tkrさんは, 難しいことでも継続して続けている方だと感じていて, 凄いなと感じました。

遭難者を救うために学内Mapを作った話 by そうた

動画

https://youtu.be/cX13Wwa0Mds?t=6228

概要

Googleマップのような操作感, GPSで場所や距離のわかる, 企画の実施場所/時間がわかるWebマップを作った話でした。

感想

自作webpackLoaderという話がわからなかったので調べました。 入門者/初心者にもわかるwebpack 4の基礎によると, webpackは以下のように書かれています。

webpackは複数のファイルをまとめる処理(bundle your asset/image/scripts/styles)を行うツール

しかし, webpackはJavaScriptファイルのみをそのままの状態で扱う必要があるため, JavaScript以外のファイルをwebpack扱う場合には, Loaderを使用する必要があります。例えば, CSSならcss-loaderとstyle-loaderが必要になります。

「自作」というのは, JS以外のローダーも書いたということですかね?もしくはそれ以外に何かあるのでしょうか?

Webサービスをリリースした話 by Momiyama

動画

https://youtu.be/cX13Wwa0Mds?t=7140

概要

コミュニティーを一元化するためにWebサービスのプラットフォームを作成した話でした。アカウントの一元化, サービスの利用者が開発出来る仕組みを作成したとのこと。

感想

このLTを見ていて驚いたのが, 参加者が開発に参加できるというシステムでした。GitHubでそのサービスはありますが, PullRequestを送るのは怖いものです。そのため, 開発に参加することを一般化して開発に取り組みやすい環境づくりをしたのは画期的だと感じました。

大学祭のシステムを作ってみる->デスマーチの始まりだった by あべけんぴ

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=7435

概要

USB接続したレシートプリンタでQRコードを生成して使用した。前回の教訓を活かし, サイズを大きくしたとのこと。

感想

レシートプリンタは遠い所にあると考えていたのですが, SDKが公開されており, ドキュメントもしっかりしているらしいです。今の所使う予定はないですが, 頭の片隅においておきます。

話がまとまらなかったので本の紹介をします by kurogoma4D

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=7854

### 概要 ソーシャルメディア進化論という本の紹介。マスメディアが一方的に情報を提供していたた時代からSNSを用いて個の集合体が情報を提供できる時代になり, 色々変わったよねという話(多分)。

感想

「これからは個人の意見がより強くなっていくのでは?」という話については同意です。個が強くなりすぎて, 妄信的に個を信じ始める人間が出てきそうな気がしました。

Vtuberをしてみた by かよ

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=8425

概要

REALITYを運用して, 話す話がないので歯磨き音と咀嚼音で配信を始めたとのことです。その結果, 意外とファンがついたそうです。

感想

思い立ったらすぐ行動するということは大事ですね。同じ環境にいた時にスタートダッシュを切れた方が絶対に良いです。Vtuberは恥ずかしい気がしますが, 羞恥心を捨ててみるのも悪くないのかもしれません(私は踏み切れませんが)。

僕とpirkaと未踏ジュニア by 糸井主歩

動画

https://youtu.be/cX13Wwa0Mds?t=9045

概要

アイヌ語で美しいという意味を持つ, pirkaという会話をしながら成長していく人工知能技術を開発した話をしていました。pirkaのシステムについても触れていました。

感想

日本語の文を処理して入力する部分が為になりました。自然言語処理はあまり詳しくないので, アプローチを知る良い機会になりました。

色の役割、色の素敵な使い方 / Let's Do Color Design by Yuki Mihashi

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=10242

概要

カラーコーディネーター検定試験1級 第2分野(商品色彩)を受験して得た知見を共有していました。実践的な内容として具体例も複数含まれていました。

感想

率直に言って, 非常に見やすいスライドだと感じました。「明るい部分は黄み, 暗い部分の色は青みにすると自然な配色(ナチュラルカラー)になる」という部分がお手軽な知見だと感じました。今後意識していこうと思います。

Backdoorの調査と解析 by task4233

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=11294

概要

Backdoorの概要と, Discord ClientをBackdoor化するSpidey Botの解析結果を紹介していた。

感想

デモやコードを見せられなかったのが残念でした。スライドで補足しておいたので, もしよければご覧ください。

50言語でハノイの塔を書いてみた by しゅーまいくん

動画

https://youtu.be/cX13Wwa0Mds?t=11615

概要

タイトル通りです。50言語でハノイの塔の解法を実装していました。実装したリポジトリはこちら。 github.com

感想

ハノイの塔を再帰で書くとシンプルに書けるのは有名ですが, 50個もの言語で実装するというのには驚きました。 ハノイの塔の再帰的解法の証明はこちらがわかりやすいと思います。

www.tbasic.org

kaggle初参加記 by ゆきりん

LT資料

speakerdeck.com

動画

https://youtu.be/cX13Wwa0Mds?t=11983

概要

Kaggleに参加した話について, 実際に何をしたのか, どういう人と共に挑戦したのかという部分にも触れつつ話していました。

感想

Kaggleはよく分からなくて, 私はタイタニックだけやってやめてしまった人間なので, 続けているのは凄いと感じました。コミュニティがあるのは羨ましいです。

VRMアバターを作った作品制作 by やはぎ

動画

https://youtu.be/cX13Wwa0Mds?t=12469

概要

VRMアバターを使って文化祭で展示するライブ映像作品を作った話でした。動きはダンス部の動きをモーションキャプチャしてモデルにのせたそうです。実際にUnityを用いてデモもしていました。

感想

VRMという言葉が初耳だったので調べると, 「プラットフォーム非依存の3Dアバターファイルフォーマット」のことのようです。

vrm.dev

VRMにすることでプラットフォームに依存しない自由なVRアバターを作ることができるようです。Unityも詳しくないのでよくわかりませんでしたが, 実際に動いていて心踊る内容でした。

自己紹介LT by Masato Sugiyama

動画

https://youtu.be/cX13Wwa0Mds?t=12811

概要

自己紹介とのことで, Youtubeチャンネルを開設した話をしていました。Youtubeチャンネルの管理者画面ではどのような光景が見えているのかと言ったことや, 1人アドベントカレンダーの話もしていました。

感想

Youtubeの管理者画面では, いつ, どの程度の人数が動画を見てくれているのかが可視化できるんですね。このような情報を元に, 動画を改善していくことができるんですね。面白かったです。

行ったLTの反省

最も良くなかったこととして, 全体の流れが分かりづらかったということが挙げられます。理由はいくつかありますが, 最も大きい理由は計画性の無さだと思います。というのも, スライドが完成したのが直前で, ぶっつけ本番のLTだったんですよね。そのせいか, 心なし早口になっていたり, 指示語を多く使ったりと全体の流れが分かりづらいLTになってしまいました。

時間が押していたというのもありますが(動画で見たら4分しか話していなかった), 落ち着いて話せるようにLT前に一度資料を見返す余裕を作っておくべきだなと感じました。

次にLTをする際は意識します。

おわりに

以上が, 学生LT#31で発表された18個のLTでした。他にも懇親会で行われたものもあったのですが, 発表者の許諾を得ていないので今回は触れません。私の専門外の分野もいくつかあったので, そのジャンルを知る良い機会になりました。

学生LTのスタッフのみなさま, 会場を提供してくださったDMM様, ならびにLT枠のプレゼンターの方々, 聴講枠で聴きにきてくださった方々には本当に感謝です。

次の学生LTはin岡山だそうです。興味のある方は参加してみてはいかがでしょうか?