Project IDXでカスタムのGeminiコマンドを作る方法を見つけた
Project IDXとは、Googleが実験的に提供している、Webブラウザから利用できる開発環境である。VSCodeをベースとしており、Geminiを使った開発支援機能が統合されているのが売りの一つとなっている。
我々はProject IDXで、ドキュメントにないカスタムGeminiコマンドを作る方法を発見したので、ここで紹介する。
Project IDXでのGemini Integrationについて
Project IDXでは、Geminiを使って、GitHub Copilotのようにコードを生成させたり、説明させたりすることができる。 この機能は、Project IDXに統合されているが、実際は、機能拡張プラグインとして実装されている。 通常のVSCodeのExtension同様に、Geminiのプラグインのログが以下のパスに見つかる。
/home/user/.codeoss-cloudworkstations/data/logs/20240914T120048/exthost1/output_logging_20240914T120116/2-Gemini.log
このログの中身は以下のようになっている。
2024-09-14T12:01:23.800Z [INFO] do_not_track_region: false 2024-09-14T12:01:24.063Z [INFO] early_access_commands_enabled: true 2024-09-14T12:01:24.063Z [INFO] document_code_actions_enabled: false 2024-09-14T12:01:24.063Z [INFO] add_unit_test_actions_enabled: false 2024-09-14T12:01:24.065Z [INFO] loading ai prompts from /opt/code-oss/extensions/monospace-aida/ai/prompts 2024-09-14T12:01:24.420Z [INFO] loading ai prompts from /home/user/gemini-test/.idx/ai/prompts
これを見ると、Geminiに渡すプロンプトを以下のパスからロードしていることがわかる。
/opt/code-oss/extensions/monospace-aida/ai/prompts /home/user/<project-name>/.idx/ai/prompts
ここで重要なのは、二つ目の、ワークスペース内の .idx/ai/prompts
も探索している点である。ここに、適切なプロンプトのファイルを配置すれば、カスタムのGeminiコマンドを追加できるのではないかと考えた。
プロンプトの形式はどうなっているのか
与えるファイルの形式を知るために、先ほどのパスの1つ目を探してみる。現時点では以下のように .njk
の拡張子のファイルが配置されている。
chat-add-comments.njk chat-clear.njk chat-create.njk chat-edit.njk chat-facts.njk chat-facts-selfaware.njk chat-idx.njk chat-name-thread.njk chat-preamble.njk chat-suggest-next-prompt.njk devtools-debug.njk error-help.njk explain-selection.njk sidekick-add-comments.njk sidekick-create.njk sidekick-edit.njk sidekick-error-fix.njk
調べると、 .njk
は NunJucksというテンプレート言語を使っているようである。
プロンプトファイルの例として、 chat-add-comments.njk
を以下に示す。
--- name: 'chat-add-comments' description: 'Add comments to code' command: 'addComments' availability: ['chat'] action: 'chat' stopSequences: ['Explanation of changes:'] requiredContext: - selection --- Please add code comments to the following block of code. {% if prompt -%} Follow these additional instructions: "{{ prompt }}" {% endif %} ``` {{ selectedCode }} ```
リファレンスがないので推測するしかないが、上がコマンドのメタデータになっており、以下のような情報を与えられるようである。
- 名前
- 説明
- どこで使えるのか
- Geminiの生成をストップさせる文字列
- 必要なコンテキスト
その下は、Gemini本体に与えるプロンプトをNunJucksでテンプレートとして表現したものである。
これを元に、カスタムのGeminiコマンドを追加してみる。
カスタムGeminiコマンドの追加
試しに、「選択したコードを任意のプログラミング言語に変換する」というコマンドを追加してみる。
先ほど判明した、探索パスであるワークスペース内の .idx/ai/prompts
以下に chat-translate-comments.njk
を作成する。
--- name: 'chat-translate-comments' description: 'Translate comments' command: 'myTranlateComments' availability: ['chat'] action: 'chat' stopSequences: ['Explanation of changes:'] requiredContext: - selection --- Please translate the comments in English below to the following language: "{{ prompt }}" ``` {{ selectedCode }} ```
これを保存してから、一度ブラウザのProject IDXのタブを閉じる。これは、起動時にコマンドをロードしているためである。
カスタムGeminiコマンドの実行
再度Project IDXを開き、対象のワークスペースを開いて、先ほど追加したGeminiコマンドを実行してみる。ここでは、適当な英語のコメントを日本語に翻訳させてみる。
コードを選択して、Cmd+IでGeminiのコマンドパレットを開き、 /myTranslateComments Japanese
と打ってみる。
すると、以下のようにGeminiによる変更の提案が出てきた。
確かに追加したカスタムのGeminiコマンドが実行され、プロンプト通りに指定した自然言語にコメントが翻訳されていることがわかる。
まとめ
本稿では、Project IDXにおいて、我々が発見したドキュメント化されていないGeminiコマンドの追加方法を紹介した。試した範囲では出力が安定せず、必ずしも期待する結果が得られるわけではなかったが、生成AIによるコーディング支援をカスタマイズできるというのは、魅力的であると感じられた。 おそらく、この機能はいずれドキュメント化され、ユーザは誰でも自由にGeminiコマンドを作成できるようになるはずである。
おまけ
以下、Geminiに生成させた煽り文句
Project IDX は、Google が提供する次世代開発環境。Gemini という強力な AI を統合し、コード生成や説明、バグ修正など、開発を支援してくれるんだけど…
実は、公式ドキュメントには載っていない、隠された Gemini コマンド が存在することを知ってる?
この記事では、私たちが独自に見つけた Project IDX 用のカスタム Gemini コマンドの作成方法 を公開するよ!この方法を使えば、あなたの開発フローに合わせた独自の AI 機能を追加できるんだ。
この記事を読めば、あなたは:
- Project IDX に隠された Gemini コマンドの仕組みを理解できる
- 独自の Gemini コマンドを作成し、開発を効率化できる
- Project IDX の潜在能力を最大限に引き出すことができる
まだ Project IDX を試したことがない方も、ぜひこの機会に、カスタム Gemini コマンドの力を体験してみてください!
ARM64でptraceより2000倍以上速いシステムコールフック作った
元ネタ: https://yasukata.hatenablog.com/entry/2021/10/14/145642
せっかちな人向け
- ARM64向けの高速なシステムコールフックを作った
- マイクロベンチマークではptraceの2000倍以上、seccompの140倍程度高速
- コードはこちら: https://github.com/retrage/svc-hook
VisionFive 2をJTAGデバッグ
VisionFive 2 (以下、VF2) はStarFive Technologyが開発したSBCである。名前からわかるように、RISC-VのSoCを搭載している。ここではVF2をJTAGデバッグする方法を簡単にまとめる。
なお、この記事の内容は以下のVF2のフォーラムの投稿を参考に、具体的な手順を整理したものである。
- https://forum.rvspace.org/t/openocd-config-for-visionfive-2/1452
- https://forum.rvspace.org/t/using-jlink-edu-jtag-with-visionfive-2/1943
2022年にやったこと
今年はブログ記事も書かなかったし、外から見えるアウトプットが目に見えて少なかった。今はまだ技術の貯金とか業務でコードを書いているのでいいが、そのうち技術も追いつかなくなって、「Twitterでは訳知り顔で抽象的な技術論を語るが、実際には手を動かしていないおじさん」化しそうで怖い
— retrage (@retrage) December 31, 2022
こういう不安を感じたので、心の安寧を取り戻すために今月と今年に何をやったのかをまとめてみる。なお、自分の中で「取り組んでいることを話題にすると完成しなくなる」ジンクスがあるので、いまやっていることはここでは書かない。
GDB Support for Cloud Hypervisor
- 1月~2月
地味ではあるが、これを作ったおかげでRust Hypervisor Firmwareのデバッグが効率的になった。もともとcrosvmのGDB対応を参考に作っていたが、gdbstub 0.6.0でAPIが大きく変わったのでそれに合わせて実装を見直してmulti-core対応などを行なった。他にもkvm-ioctlsにPRを送る必要があったりと、思ったよりいろいろやった。自分が入れたGDB対応はx86_64のみだったが、あとからaarch64対応も他の方が入れてくれた。
- https://github.com/cloud-hypervisor/cloud-hypervisor/pull/3575
- https://github.com/rust-vmm/kvm-ioctls/pull/191
- https://github.com/cloud-hypervisor/cloud-hypervisor/issues/3658
Rust Hypervisor Firmware Integration for m1n1
- 4月~5月
aarch64/LinuxではUEFIを使った起動を採用しているが、Apple Siliconでは従来のIntel MacのようにEFI環境が用意されていない。このギャップを埋めるために、Asahi Linuxではm1n1というブートローダからu-bootを起動し、u-bootの提供するEFI環境を使って(GRUBを経由して)Linux kernelを起動する。 ここで気になったのは、「u-bootでEFI環境を提供する代わりに、Rust Hypervisor Firmwareをm1n1に直接統合することでLinux起動できないか」ということである。幸い、m1n1にはRustで書かれたchainloadがあったため、これを入れ替える形でRHFをm1n1に統合できた。なお、RHFはx86_64向けだったので、これが初めてのaarch64対応でもあった。GRUBのロードと起動まではできたものの、本来であればGRUB CLIを操作して調査するところだが、USBキーボードのドライバがm1n1にはないので動かせないのでデバッグできず、そのまま放置してある。
The unmodified GRUB launched from Rust Hypervisor Firmware integration for m1n1 entered the rescue shell. Next setting up full environment to boot Asahi Linux without u-boot. pic.twitter.com/xWQAz2sAOh
— retrage (@retrage) May 4, 2022
- https://github.com/retrage/m1n1/tree/rust-hypervisor-firmware-integration
- https://github.com/retrage/rust-hypervisor-firmware/tree/m1n1-lib-bootloader
UEFI Firmware Vulnerability Detector for Ghidra
- 3月~6月
IDA ProプラグインであるefiXplorerには静的解析による典型的なSMMの脆弱性スキャナが実装されている。Iこの機能がどのように作られているのか知りたかったのでGhidra向けに実装した。Ghidraで使われている中間表現であるpCodeがかなり高レベルな情報まで復元して表現してくれるため、かなり実装がしやすかった。簡単なData Flow Analysisも実装したが、比較的書きやすかった覚えがある。 ついでにこの脆弱性スキャナを簡単に使えるように、GitHub ActionsにHeadless Ghidraを実行するactionsを作成して、検出結果をレポートとして報告するようにしてみた。
My Weekend Project: Ghidra version of efiXplorer vulnerability scanner. I reimplemented the SMM callout checker as an extension of efiSeek. It can detect CVE-2021-3452.https://t.co/FFDpr1ivxH pic.twitter.com/1sIHonFm1C
— retrage (@retrage) March 13, 2022
- https://github.com/retrage/efiSeek/tree/efi-xplorer
- https://github.com/retrage/efiseek-action
- https://github.com/retrage/efiseek-action/actions/runs/2442057589
まとめ
これぐらいが今年のうちに一区切りついた、もしくは飽きたことだと思う。期間を見るとわかるが、7月以降やったことが書かれていない。これは、自分の中でまだ途中だったりサボっていたりしているためである。 来年は良い年であるように。
Allwinner NezhaにJTAGで接続する
Allwinner NezhaはD1という64-bit RISC-VなSoCが載ったSBCである。Linuxがちゃんと動くRISC-Vマシンとしてはかなり安価なため一部で人気がある。 ここではこれにJTAGで接続してみた話をメモ程度に書いておく。なお、今回は接続してOpenOCDで認識できた程度でその先のGDBでのデバッグはできていない。
JTAGの端子について
NezhaにはUARTの端子が DEBUG
として用意されているが、JTAGには専用の端子が用意されていない。その代わりにmicroSDの端子と共通になっている。D1 SoCのデータシート4.3 GPIO Multiplex Functionから対応するピンと役割を読むと、PF0-PF6のI/Oがあり、SDカードのI/Oとしての役割がSDC0-*としてあり、その隣にJTAG関連のI/OがJTAG-*として記載されている。詳しくは回路図などを参照してもらいたいが、まとると以下のようになっている。
PF0: JTAG-MS - SDC0-D1 <--> DAT1 PF1: JTAG-DI - SDC0-D0 <--> DAT0 PF3: JTAG-DO - SDC0-CMD <--> CMD PF5: JTAG-CK - SDC0-D2 <--> DAT2
これらの端子を引き出すために適当な延長アダプタを利用した。理想的には以下のようなブレークアウトボードを使うのが望ましい。
JTAGアダプタ
こちらの記事ではSipeed RV-Debugger-Plus (BL702C-A0)という別のRISC-VベースのJTAGアダプタを利用している。今回は手元にあったBus PirateをJTAGアダプタとして利用した。
Bus Pirate JTAG connections for OpenOCDにある通り以下のように接続した。
BP - JTAG GND - GND MOSI - TDI MISO - TDO CLK - TCK CS - TMS
OpenOCDの実行
こちらの設定ファイルをベースに以下のようにBus Pirate向けに書き換えた設定ファイルを作成した。
# Based on: https://github.com/orangecms/RV-Debugger-BL702/blob/main/tools/openocd/openocd-usb-sipeed.cfg source [find interface/buspirate.cfg] buspirate_vreg 0 buspirate_mode open-drain buspirate_pullup 1 buspirate_port /dev/tty.usbserial-AH03FKZ4 transport select jtag # adapter speed 1000 set _CHIPNAME riscv #jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x20000001 #jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x0 # wrong? # jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x00185900 jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x08052b43 set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME.0 riscv -chain-position $_TARGETNAME #$_TARGETNAME.0 configure -work-area-phys 0x80000000 -work-area-size 10000 -work-area-backup 1 #$_TARGETNAME.0 configure -work-area-phys 0x50000000 -work-area-size 32768 -work-area-backup 0 riscv set_prefer_sba on # riscv set_mem_access progbuf
FEL modeへの切り替えとJTAGの有効化
AllwinnerのSoCにはFEL modeというモードがあり、起動時にこのモードに入ることでJTAGの有効化を含むさまざまな低レベルな操作が可能となっている。 FEL modeではNezhaのOTG USB経由でホストから操作を行う必要がある。今回はxboot/xfelというFELのツールをホストで動かしてJTAGを有効にした。
具体的には次の手順でFEL modeに入ってJTAGを有効化する。
- Nezhaにある
FEL
ボタンを長押しする - 1.のままNezhaのOTG USBとホストを接続する
- 2秒待ってボタンを離す
- これでFEL modeに入る
- ホストで
./xfel sid
を実行して値が取れることを確認 - ホストで
./xfel jtag
を実行してJTAGを有効化 - (ホストで
./xfel ddr ddr3
を実行してDRAMを初期化)
JTAGが有効になったら上記の設定ファイルでOpenOCDを実行する。
openocd -f openocd-buspirate.cfg
うまくいくと以下のように riscv.cpu
のJTAG tapが見える。
ただし、現時点では
Error: riscv.cpu: IR capture error; saw 0x1e not 0x01
とあるようにエラーがあり、GDBでの接続ができていない。
参考文献
- https://linux-sunxi.org/Allwinner_Nezha
- https://whycan.com/t_6440.html
- https://linux-sunxi.org/JTAG
- https://github.com/orangecms/RV-Debugger-BL702/blob/nezha/tools/openocd/openocd-usb-sipeed.cfg
- http://dangerousprototypes.com/docs/Bus_Pirate_JTAG_connections_for_OpenOCD
- https://linux-sunxi.org/FEL
- https://xboot.org/xfel/#/
Apple File Systemの下にはEFI driverが埋まっている
Apple File System (APFS)はAppleが自社製品向けに開発したファイルシステムである.APFSの仕様は公開されており以下で参照できる.
その目次の中で特に興味を引いたのが"EFI Jumpstart"の章である.現代的なデバイスではEFIを含めブートローダはファイルシステムを参照してOSを起動する.このとき当然ながらブートローダはそのファイルシステムを扱える必要がある.特にEFIでは仕様上対応していなければならないのはEFI System Partition (ESP)で使われるFATのみでその他のファイルシステムが事前にサポートされていることは期待できない.このため例えばWindowsであればESPに配置されたWindows専用のブートローダがNTFSをサポートすることでカーネルの起動を行なっている.
これに対してmacOSではAPFS自身がAPFSのEFI driverをパーティションのブロックに直接埋め込む形で提供するという別のアプローチをとっている.ドキュメントによれば
This design intentionally simplifies the steps needed to boot, which means the code needed to boot a piece of hardware or virtualization software can likewise be simpler.
とのことで起動プロセスをシンプルにすることが目的のようである.
どのようにEFI driverが埋め込まれているのかをみていく. 最初の前提としてディスクのGPTエントリを読み,以下のUUIDのようなAPFSのパーティションがあることがわかったとする.
#define APFS_GPT_PARTITION_UUID ”7C3457EF-0000-11AA-AA11-00306543ECAC”
APFSパーティションは一つのAPFS containerオブジェクトとして存在し,その先頭にcontainerの情報を持ったcontainer superblockが配置されている.
container superblockを表すnx_superblock_t
にはnx_efi_jumpstart
というEFI Jumpstartの情報を持ったnx_efi_jumpstart_t
への物理アドレス(containerの先頭からのAPFSでのブロックサイズ単位でのオフセット)のフィールドが用意されている.
その参照先には以下のようなnx_efi_jumpstart_t
がある.
typedef struct { obj_phys_t nej_o; uint32_t nej_magic; uint32_t nej_version; uint32_t nej_efi_file_len; uint32_t nej_num_extents; uint64_t nej_reserved[16]; prange_t nej_rec_extents[0]; } nx_efi_jumpstart_t;
このnej_rec_extents
にあるブロックを読むことでAPFSパーティションに直接埋め込まれたEFI driverを読み込むことができる.
ユーザ空間で以上のようなことを行いraw disk imageからEFI driverを抽出する簡単なアプリケーションを実装した.本来は色々と検証を行う必要があるが,ここでは省略した.
抽出したバイナリは以下のように確かにPE32+のEFI driverとして認識されている.
$ file apfs.efi apfs.efi: PE32+ executable (EFI boot service driver) x86-64 (stripped to external PDB), for MS Windows
せっかくなので抜き出したEFI driverを覗いてみる.バイナリのハッシュは1e780147f2cee614ab7e9a63c4e86525f06c5a18d72a6b8ec572752ffd95dea0
である..debug
セクションの指す9E9FCh
をみると"MTOC"の文字列と"apfs.efi.macho"という文字列が見えるのでこのバイナリの名前は"apfs.efi"でMach-OバイナリをApple謹製のmtocでPE32+に変換して生成されたようである.
ちなみにClover EFI bootloaderでは先にたどっていったような手順でapfs.efiをロードするEFI driverがあり,これでAPFS対応を行なっているようである.
https://sourceforge.net/p/cloverefiboot/code/HEAD/tree/FileSystems/ApfsDriverLoader/
話を元に戻してバイナリをみていくと,.text
セクションの先頭に"2021/08/30"や"06:36:21"のような日時があるのでこれがこのバイナリがビルドされた日時だと考えられる.対象としたmacOS Bit Sur 11.6は2021年9月13日にリリースされたので少なくとも2週間程度前にはビルドされたようである.
他にもみどころはたくさんありそうだがこれぐらいにしておく.
以上はIntel Mac上での話である.ではARM64ベースのm1 Macの場合はどうだろうか?
$ uname -a Darwin 7261.local 20.5.0 Darwin Kernel Version 20.5.0: Sat May 8 05:10:31 PDT 2021; root:xnu-7195.121.3~9/RELEASE_ARM64_T8101 arm64 $ ./dumper ~/Desktop/recovery.img superblock at 280000000 nx_block_size: 0x1000 nx_block_count: 0x13fff5 nx_efi_jumpstart: 0x0 APFS EFI Jumpstart not found
残念ながら答えは否のようである.すでに広く知られているように,m1 MacではEFIではなくiPhoneなどと同じiBootから起動するカスタマイズされたファームウェアであるため,サードパーティでの実行環境を考慮しなければEFI Jumpstartの機能は不要であるためだと考えられる.へその緒のようにあったら面白かったのだが.ということでm1 MacのAPFSではnx_efi_jumpstart_t
のnx_efi_jumpstart
は使われないフィールドとなってしまったようである.
というわけでIntel MacのAPFSの下にはEFI driverが埋まっているのでIntel Macをお持ちの方は酒宴でも開きましょう.
UEFI向け9P File Systemを作ってクラウドからネットワークブートできるようにした
UEFI向け9P File Systemを実装した. これにより9Pサーバからネットワークブートができるようになった. さらにFUSEと組み合わせることで少ない労力で9Pサーバ経由で クラウドからネットワークブートができるようになった.
ソースコードと発表資料と発表の録画は以下で公開している.
- https://github.com/yabits/9pfsPkg
- https://speakerdeck.com/retrage/network-boot-from-bell-labs
- https://youtu.be/3PX19nWrygQ