WPP
Gazebo ionic で SDF にカメラを追加した

少し前から続いている gazebo ネタですが、シミュレーションの中にカメラを置きたくて試行錯誤しました。結果からいうと github の gz-sim に example がありそこを参考にしました。

カメラを Gazabo のワールドにいれてある特定のことをしたいのですが、自分には、 ROS / ROS2 の経験はほぼない状態なので URDF ではなくひとまず SDF で書くことにしています。そうでないと 2 重 3 重の障害となりなかなか全体の理解がすすまないだろうという判断です。

Gazebo 公式のチュートリアルにもセンサーのサンプルはありますが、あれはシステムプラグインなので <plugin> タグの中の filename をどう書いていいのかイマイチわかりませんでした。

公式ドキュメントを漁っているといつのまに Github の example にたどり着き world フォルダに色々なサンプルがあることに気づきました。

CameraSensor の例

最低限のモデルしかおいていないです。このコードのままだとどうもカメラの向きが悪いようで ワールドの中にある球体をカメラの画角に捉えられていません。が、座標適当に調整すればカメラで捉えるはずです。

<?xml version="1.0" ?>
<sdf version="1.10">
  <world name="camera_world">
    <plugin
        filename="gz-sim-physics-system"
        name="gz::sim::systems::Physics">
    </plugin>

    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    <gravity>0 0 -9.8</gravity>

    <light type='directional' name='sun'>
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
        <static>true</static>
        <link name="link">
            <collision name="collision">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                </plane>
            </geometry>
            </collision>
            <visual name="visual">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                <size>100 100</size>
                </plane>
            </geometry>
            <material>
                <ambient>0.8 0.8 0.8 1</ambient>
                <diffuse>0.8 0.8 0.8 1</diffuse>
                <specular>0.8 0.8 0.8 1</specular>
            </material>
            </visual>
        </link>
    </model>

    <model name='sphere1'>
      <pose>1.5 1.5 0.5  0 0 0</pose>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </collision>
        <visual name='visual'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </visual>
      </link>
    </model>

    <!-- RGB camera-->
    <model name='rgb_camera'>
      <pose>0 0 0.5  0 0 0</pose>
      <link name='link'>
        <inertial>
          <mass>0.1</mass>
          <inertia>
            <ixx>0.000166667</ixx>
            <iyy>0.000166667</iyy>
            <izz>0.000166667</izz>
          </inertia>
        </inertial>

        <collision name='collision'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
        </collision>

        <visual name='visual'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.1 0.1 0.1 1</ambient>
            <diffuse>0.1 0.1 0.1 1</diffuse>
            <specular>0.01 0.01 0.01 1</specular>
          </material>
        </visual>
        
        <sensor name='camera' type='camera'>
          <always_on>1</always_on>
          <update_rate>2</update_rate>
          <visualize>true</visualize>
          <camera>
            <horizontal_fov>1.047</horizontal_fov>
            <image>
              <width>640</width>
              <height>480</height>
              <format>R8G8B8</format>
            </image>
            <clip>
              <near>0.1</near>
              <far>100</far>
            </clip>
            <save enabled='true'>
              <path>images/</path>
            </save>
          </camera>
          <topic>rgb</topic>
        </sensor>
      </link>
    </model>
    <!-- RGB camea end -->

    <!-- load sensors plugin  -->
    <plugin filename='gz-sim-sensors-system'
      name='gz::sim::systems::Sensors'>
    </plugin>
  </world>

</sdf>

カメラの画像は images フォルダに保存されます。もしかすると予めフォルダを作成する必要があるかもしれません。

gazebo と一緒に組み込まれる sensors は、どうやら gz-sim-sensors-system というファイル名で参照できるようです。

動作を確認するには、ターミナルを 2 つ立ち上げ、一方の端末でシミュレータを起動します。

$ gz sim camera.sdf

もう一方の端末でトピックをリッスンします。

 gz topic -e -t /rgb

シミュレータの左下の開始?再生?ボタンをクリックすると、トピックを聴いている端末なにやらデータが流れ出し images フォルダにカメラが捉えた映像が保存されます。

depth_camera の例

基本的に、通常のカメラと同じなのですが、まずは sdf を提示します。

<?xml version="1.0" ?>
<sdf version="1.10">
  <world name="depth_world">
    <plugin
        filename="gz-sim-physics-system"
        name="gz::sim::systems::Physics">
    </plugin>

    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    <gravity>0 0 -9.8</gravity>

    <light type='directional' name='sun'>
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground_plane">
        <static>true</static>
        <link name="link">
            <collision name="collision">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                </plane>
            </geometry>
            </collision>
            <visual name="visual">
            <geometry>
                <plane>
                <normal>0 0 1</normal>
                <size>100 100</size>
                </plane>
            </geometry>
            <material>
                <ambient>0.8 0.8 0.8 1</ambient>
                <diffuse>0.8 0.8 0.8 1</diffuse>
                <specular>0.8 0.8 0.8 1</specular>
            </material>
            </visual>
        </link>
    </model>

    <model name='sphere1'>
      <pose>1.5 1.5 0.5  0 0 0</pose>
      <link name='link'>
        <collision name='collision'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </collision>
        <visual name='visual'>
          <geometry>
            <sphere>
              <radius>0.5</radius>
            </sphere>
          </geometry>
        </visual>
      </link>
    </model>

    <!-- depth camera-->
    <model name='depth_camera'>
      <pose>0 0 0.5  0 0 0</pose>
      <link name='link'>
        <inertial>
          <mass>0.1</mass>
          <inertia>
            <ixx>0.000166667</ixx>
            <iyy>0.000166667</iyy>
            <izz>0.000166667</izz>
          </inertia>
        </inertial>

        <collision name='collision'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
        </collision>

        <visual name='visual'>
          <geometry>
            <box>
              <size>0.1 0.1 0.1</size>
            </box>
          </geometry>
          <material>
            <ambient>0.1 0.1 0.1 1</ambient>
            <diffuse>0.1 0.1 0.1 1</diffuse>
            <specular>0.01 0.01 0.01 1</specular>
          </material>
        </visual>
        
        <sensor name='depth_camera' type='depth_camera'>
          <always_on>1</always_on>
          <update_rate>1</update_rate>
          <visualize>true</visualize>
          <camera>
            <horizontal_fov>1.047</horizontal_fov>
            <image>
              <width>640</width>
              <height>480</height>
              <format>L8</format>
            </image>
            <clip>
              <near>0.1</near>
              <far>100</far>
            </clip>
            <save enabled='true'>
              <path>images</path>
            </save>
          </camera>

          <topic>depth</topic>
        </sensor>
      </link>
    </model>
    <!-- depth camea end -->

    <!-- load sensors plugin  -->
    <plugin filename='gz-sim-sensors-system'
      name='gz::sim::systems::Sensors'>
    </plugin>
  </world>

</sdf>

先程の camera_sensor のときとほぼ同じですが、 <sensor> の type 属性がdepth_camera になっています。更に距離情報はカラーだと困るので、SDFormat の仕様を見て適当に L8 としたがこれが正しいのかは全くわかっていません。

一応つぶやく トピック depth に変えていますが、まあ変えなくてもいいです。

動作確認はcamera_sensor のときと同様におこないますが、 リッスンするトピックと SDF 内で指定した depth にしてやる必要があります。

ようやく、カメラデータの吐き出しまでできました。

とはいえ、rgbd_camera は同じようにやってもダンマリなのでなかなか先に進めない。同じフォルダにある sensors_demo.sdf には rgbd_camera の例があるのでこれを参考にやってみるか。

参考

gz-sim/examples/worlds at gz-sim9 · gazebosim/gz-sim

SDFormat Specification

g

Google Maps JS API を読み込むと uBlock Origin が maps.googleapis.com/maps/api/mapsjs/gen_204 をブロックしてしまう

ちょっとした調査で Google maps API を動かしているのですが、こんな感じでリクエストをが失敗してしまします。

結果としては uBlock Origin (UBO と略すらしい) の不具合ではなく フィルターリストの不適合だった。

Firefox の Dev Tools の Transferred には “Blocked By uBlock Origin” と表示されています。

そこで uBlock Origin のログ画面を開き再度リクエストしてみると “EasyPrivacy” というフィルターリストにヒットしてブロックされたことがわかりました。

フィルターリストの該当部分だけを無効化や削除できればいいのですが、ちょっとやり方がわからなかったので一旦 EasyPrivacy を無効にすることで回避しました。

ちょうど今日から uBlock Origin が Chrome 系ブラウザで使えなくなったようだがとりあえず Chrome 系はメインでないので様子見する。別のプラグインに移行するか UBO のアップデート待ちすることになるだろう。

Gazebo のチュートリアルをかじった

仕事でロボット系のシミュレーションが必要になるかもしれないってことで Gazebo のチュートリアルをちょっとだけやってみた。

とりあえず Moving the Robot まで進めた。

環境

  • Linux Mint 22.1 (ubuntu 22.04)
  • gazeob ionic

Gazebo 自身は、公式の手順で apt install gz-ionic でインストールした。

Gazebo のチュートリアルを Moving the Robot まで進めると building_robot.sdf はこうなった。
各章の冒頭に完成形があるのでそれをみればいい。

<?xml version="1.0" ?>
<sdf version="1.10">
    <world name="Moving_robot">
        <physics name="1ms" type="ignored">
            <max_step_size>0.001</max_step_size>
            <real_time_factor>1.0</real_time_factor>
        </physics>
        <plugin
            filename="gz-sim-physics-system"
            name="gz::sim::systems::Physics">
        </plugin>
        <plugin
            filename="gz-sim-user-commands-system"
            name="gz::sim::systems:aze:UserCommands">
        </plugin>
        <plugin
            filename="gz-sim-scene-broadcaster-system"
            name="gz::sim::systems::SceneBroadcaster">
        </plugin>

        <light type="directional" name="sun">
            <cast_shadows>true</cast_shadows>
            <pose>0 0 10 0 0 0</pose>
            <diffuse>0.8 0.8 0.8 1</diffuse>
            <specular>0.2 0.2 0.2 1</specular>
            <attenuation>
                <range>1000</range>
                <constant>0.9</constant>
                <linear>0.01</linear>
                <quadratic>0.001</quadratic>
            </attenuation>
            <direction>-0.5 0.1 -0.9</direction>
        </light>

        <model name="ground_plane">
            <static>true</static>
            <link name="link">
                <collision name="collision">
                <geometry>
                    <plane>
                    <normal>0 0 1</normal>
                    </plane>
                </geometry>
                </collision>
                <visual name="visual">
                <geometry>
                    <plane>
                    <normal>0 0 1</normal>
                    <size>100 100</size>
                    </plane>
                </geometry>
                <material>
                    <ambient>0.8 0.8 0.8 1</ambient>
                    <diffuse>0.8 0.8 0.8 1</diffuse>
                    <specular>0.8 0.8 0.8 1</specular>
                </material>
                </visual>
            </link>
        </model>

        <model name='vehicle_blue' canonical_link='chassis'>
            <pose relative_to='world'>0 0 0 0 0 0</pose>   <!--the pose is relative to the world by default-->

            <link name='chassis'>
                <pose relative_to='__model__'>0.5 0 0.4 0 0 0</pose>
                <inertial> <!--inertial properties of the link mass, inertia matix-->
                    <mass>1.14395</mass>
                    <inertia>
                        <ixx>0.126164</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.416519</iyy>
                        <iyz>0</iyz>
                        <izz>0.481014</izz>
                    </inertia>
                </inertial>
                <visual name='visual'>
                    <geometry>
                        <box>
                            <size>2.0 1.0 0.5</size> <!--question: this size is in meter-->
                        </box>
                    </geometry>
                    <!--let's add color to our link-->
                    <material>
                        <ambient>0.0 0.0 1.0 1</ambient>
                        <diffuse>0.0 0.0 1.0 1</diffuse>
                        <specular>0.0 0.0 1.0 1</specular>
                    </material>
                </visual>
                <collision name='collision'> <!--todo: describe why we need the collision-->
                    <geometry>
                        <box>
                            <size>2.0 1.0 0.5</size>
                        </box>
                    </geometry>
                </collision>
            </link>

            <!--let's build the left wheel-->
            <link name='left_wheel'>
                <pose relative_to="chassis">-0.5 0.6 0 -1.5707 0 0</pose> <!--angles are in radian-->
                <inertial>
                    <mass>2</mass>
                    <inertia>
                        <ixx>0.043333</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.043333</iyy>
                        <iyz>0</iyz>
                        <izz>0.08</izz>
                    </inertia>
                </inertial>
                <visual name='visual'>
                    <geometry>
                        <cylinder>
                            <radius>0.4</radius>
                            <length>0.2</length>
                        </cylinder>
                    </geometry>
                    <material>
                        <ambient>1.0 0.0 0.0 1</ambient>
                        <diffuse>1.0 0.0 0.0 1</diffuse>
                        <specular>1.0 0.0 0.0 1</specular>
                    </material>
                </visual>
                <collision name='collision'>
                    <geometry>
                        <cylinder>
                            <radius>0.4</radius>
                            <length>0.2</length>
                        </cylinder>
                    </geometry>
                </collision>
            </link>

            <!--copy and paste for right wheel but change position-->
            <link name='right_wheel'>
                <pose relative_to="chassis">-0.5 -0.6 0 -1.5707 0 0</pose> <!--angles are in radian-->
                <inertial>
                    <mass>1</mass>
                    <inertia>
                        <ixx>0.043333</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.043333</iyy>
                        <iyz>0</iyz>
                        <izz>0.08</izz>
                    </inertia>
                </inertial>
                <visual name='visual'>
                    <geometry>
                        <cylinder>
                            <radius>0.4</radius>
                            <length>0.2</length>
                        </cylinder>
                    </geometry>
                    <material>
                        <ambient>1.0 0.0 0.0 1</ambient>
                        <diffuse>1.0 0.0 0.0 1</diffuse>
                        <specular>1.0 0.0 0.0 1</specular>
                    </material>
                </visual>
                <collision name='collision'>
                    <geometry>
                        <cylinder>
                            <radius>0.4</radius>
                            <length>0.2</length>
                        </cylinder>
                    </geometry>
                </collision>
            </link>

            <frame name="caster_frame" attached_to='chassis'>
                <pose>0.8 0 -0.2 0 0 0</pose>
            </frame>

            <!--caster wheel-->
            <link name='caster'>
                <pose relative_to='caster_frame'/>
                <inertial>
                    <mass>1</mass>
                    <inertia>
                        <ixx>0.016</ixx>
                        <ixy>0</ixy>
                        <ixz>0</ixz>
                        <iyy>0.016</iyy>
                        <iyz>0</iyz>
                        <izz>0.016</izz>
                    </inertia>
                </inertial>
                <visual name='visual'>
                    <geometry>
                        <sphere>
                            <radius>0.2</radius>
                        </sphere>
                    </geometry>
                    <material>
                        <ambient>0.0 1 0.0 1</ambient>
                        <diffuse>0.0 1 0.0 1</diffuse>
                        <specular>0.0 1 0.0 1</specular>
                    </material>
                </visual>
                <collision name='collision'>
                    <geometry>
                        <sphere>
                            <radius>0.2</radius>
                        </sphere>
                    </geometry>
                </collision>
            </link>


            <!--connecting these links together using joints-->
            <joint name='left_wheel_joint' type='revolute'> <!--continous joint is not supported yet-->
                <pose relative_to='left_wheel'/>
                <parent>chassis</parent>
                <child>left_wheel</child>
                <axis>
                    <xyz expressed_in='__model__'>0 1 0</xyz> <!--can be defined as any frame or even arbitrary frames-->
                    <limit>
                        <lower>-1.79769e+308</lower>    <!--negative infinity-->
                        <upper>1.79769e+308</upper>     <!--positive infinity-->
                    </limit>
                </axis>
            </joint>

            <joint name='right_wheel_joint' type='revolute'>
                <pose relative_to='right_wheel'/>
                <parent>chassis</parent>
                <child>right_wheel</child>
                <axis>
                    <xyz expressed_in='__model__'>0 1 0</xyz>
                    <limit>
                        <lower>-1.79769e+308</lower>    <!--negative infinity-->
                        <upper>1.79769e+308</upper>     <!--positive infinity-->
                    </limit>
                </axis>
            </joint>

            <!--different type of joints ball joint--> <!--defult value is the child-->
            <joint name='caster_wheel' type='ball'>
                <parent>chassis</parent>
                <child>caster</child>
            </joint>

            <!--diff drive plugin-->
            <plugin
                filename="gz-sim-diff-drive-system"
                name="gz::sim::systems::DiffDrive">
                <left_joint>left_wheel_joint</left_joint>
                <right_joint>right_wheel_joint</right_joint>
                <wheel_separation>1.2</wheel_separation>
                <wheel_radius>0.4</wheel_radius>
                <odom_publish_frequency>1</odom_publish_frequency>
                <topic>cmd_vel</topic>
            </plugin>
        </model>

        <!-- Moving Left-->
        <plugin filename="gz-sim-triggered-publisher-system"
                name="gz::sim::systems::TriggeredPublisher">
            <input type="gz.msgs.Int32" topic="/keyboard/keypress">
                <match field="data">16777234</match>
            </input>
            <output type="gz.msgs.Twist" topic="/cmd_vel">
                linear: {x: 0.0}, angular: {z: 0.5}
            </output>
        </plugin>
        <!-- Moving Forward-->
        <plugin filename="gz-sim-triggered-publisher-system"
                name="gz::sim::systems::TriggeredPublisher">
            <input type="gz.msgs.Int32" topic="/keyboard/keypress">
                <match field="data">16777235</match>
            </input>
            <output type="gz.msgs.Twist" topic="/cmd_vel">
                linear: {x: 0.5}, angular: {z: 0.0}
            </output>
        </plugin>
        <!-- Moving Right-->
        <plugin filename="gz-sim-triggered-publisher-system"
                name="gz::sim::systems::TriggeredPublisher">
            <input type="gz.msgs.Int32" topic="/keyboard/keypress">
                <match field="data">16777236</match>
            </input>
            <output type="gz.msgs.Twist" topic="/cmd_vel">
                linear: {x: 0.0}, angular: {z: -0.5}
            </output>
        </plugin>
        <!-- Moving Backward-->
        <plugin filename="gz-sim-triggered-publisher-system"
                name="gz::sim::systems::TriggeredPublisher">
            <input type="gz.msgs.Int32" topic="/keyboard/keypress">
                <match field="data">16777237</match>
            </input>
            <output type="gz.msgs.Twist" topic="/cmd_vel">
                linear: {x: -0.5}, angular: {z: 0.0}
            </output>
        </plugin>
    </world>
</sdf>

起動するには、ターミナルを2つ起動する必要がある。

1つめは、sdf ファイルを読み込んで画面を表示する

$ gz sim building_robot.sdf

もう一つは、入力されたキーをターミナルにダンプするものなので本当は動かさなくてもいい。

$ gz topic -e -t /keyboard/keypress

そして GUI で Keyboard Publisher を起動する必要がある。
GUI でプラグインを起動するには、右上のメニューアイコンで 『Key Publisher』 を選択する。
これを読み飛ばしてしばらく考え込んでしまった。

そして左下の 再生ボタン? 開始ボタンをクリックする。

するとキーボードの操作で車?が動作するようになる。

参考

Moving the robot — Gazebo ionic documentation

gazeboとROS2を連携してロボットを操作する #Gazebo – Qiita

自ホストの外側の IP アドレスを curl で取得する。

IP アドレスでアクセス制限すfる場合に自ネットワークの外側からみた IP アドレスを調べたい場合がある。外部の API サーバーや Web サーバーが接続元の IP アドレスで制限するような場合だ。

ローカルのルーターに入れば確認することは当然にできるがまあ面倒です。curl でアクセスすると自ネットの IP アドレスを返してくれるサイトがあります。以前から ipinfo.io は知ってましたがこのサイトだと IPv4 しか返してくれません。(恐らく IPv4 でしか接続を受け付けてくれないのでしょう。)

Claude に “curl で自ホストのIPアドレスを確認できるサイトをリストアップして” と聞いてみると以下の回答を返してくれました。

IP アドレスを確認できるウェブサイトに対して、curl コマンドでアクセスできるものをリストアップします。以下のサイトでは外部から見たあなたの IP アドレスを確認できます:

1. `curl ifconfig.me`
2. `curl ipinfo.io/ip`
3. `curl api.ipify.org`
4. `curl checkip.amazonaws.com`
5. `curl icanhazip.com`
6. `curl ipecho.net/plain`
7. `curl ip.tyk.nu`
8. `curl wtfismyip.com/text`
9. `curl ipaddress.sh`
10. `curl myip.dnsomatic.com`

これらのサイトはプレーンテキストで IP アドレスだけを返すため、curl コマンドと相性が良いです。追加情報が必要な場合は、例えば `curl ipinfo.io` のようにすると、IP アドレスだけでなく地理的な位置情報なども取得できます。

ひとつづつ確認してみると、IPv6 と IPv4 を返すサイトが半々くらいでした、10 はアクセスできませんでした。で、自分にはこれらのサイト名はちょっと覚えにくかったので IPv6 と IPv4 を両方とも表示するスクリプトにしました。

# get outside IP address. this script gets IPv6 and IPv4 address both.

#!/bin/bash

# for IPv6
echo -n "IPv6: "

# 出力に改行がないパターン
curl ipecho.net/plain
# curl ipecho.net/plain
# curl ip.tyk.nu
echo ""

# 出力に改行のあるパターン
# curl wtfismyip.com/text
# curl ifconfig.me
# curl icanhazip.com
# curl wtfismyip.com/text

# for IPv4
echo -n "IPv4: "
curl ipinfo.io/ip
#curl api.ipify.org
echo ""

# 出力に改行のあるパターン
# curl checkip.amazonaws.com
# curl ipaddress.sh
RUSTDESK がいい感じ

これまで linux での VNC の設定がいまだによくわからないので、nomachine をリモートデスクトップ接続に使用していました。

数あるリモートデスクトップアプリの中でも nomachine は linux, Mac, Raspberry pi, Windows に対応し都合が良かったからです。

一方で Windows ドメイン (Active Directory) に参加している Windows にリモート PC からアクセスすると度々認証まわりのエラーで接続できないことが多くちょっと面倒だとおもっていました。
具体的には設定等を変更していないのに、あるときは接続に失敗したり、別のときは成功したりといった現象にわずらわされていました。これは推測ですが、Windows ドメイン認証は、社内ネットワークに接続していない状態でPC にログインできるように認証情報がキャッシュされる仕組みがあったはずです。このキャッシュでの認証できる状態だとどうやらログインできている気がする。(反対向きにいうとネットワーク経由でログインしたときは、どうもnomachine で必要な権限が許可されていないようだ。)

話がながくなったが、上に閉口して移転先を探してみた。

候補としては、X2Go、RUSTDESK、Dayon が見つかったが今回は RUSTDESK を使用することにした。

Windows でのインストール

scoop にパッケージがあったので `> scoop install rustdesk` でインストールした。

Linux でのインストール

LM22 では デフォルトのアプリストアに flatpak で掲載されていたので flatpak でインストールした。

2025-3-25 追記:

LM22 で RUSTDESK をインストールしただけの状態では、デスクトップセッション(X11)を開いてからしか使えないようなので、自動ログインするように設定した上で、 [Startup Applications] で RUSTDESK を起動するようにした。

Raspberry pi でのインストール

参考ページの Pi-Apps というのを経由して入れてみた。RUSTDESK 自身は github にあるんですが、Version-Upの度に手動インストールは結構だるいので、Pi-Apps を使ってみることにした。

使用方法

説明を明確にするための用語の定義

リモート PC: リモートからアクセスさせたいPC (画面なしPC や手元以外のPC)

ローカル PC: 自画面に外部PC の画面を表示してコントロールする PC

  1. リモート PC で RUSTDESK を起動する。
    • この PC をヘッドレス運用したい場合は、固定パスワードを指定する。
    • リモートPC の ID とパスワード をメモる (メモらなくてもいいがこの PC の ID でリモートから接続する)
  2. ローカル PC でも同じように RUSTDESK を起動する。
  3. リモート PC の ID で接続開始する。
  4. リモート PC のワンタイムパスワードか固定パスワードで認証要求をかける
  5. リモート PC の画面が表示される。

あくまで体感でしかないが、nomachine や VNC よりもキビキビと動いているきがする。画面表示もクッキリなきがしていいかもしれない。

当面はこれを使っていこう。

参考

Install RustDesk on Raspberry Pi | Pi-Apps

rustdesk/rustdesk: An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.

ラズベリーパイに eza をインストール

eza は ls の代替コマンドでいい感じに表示してくれるやつ。LM22 や Ubuntu では気に入って使用しているが RaspberryOS ではデフォルトではインストールできないようだ。

公式の説明では以下の通りだが、RaspberryOS (bullseye) では 若干ディレクトリ構成がことなるので調整する。

$ sudo mkdir -p /etc/apt/keyrings
$ wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list
$ sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list
$ sudo apt update
$ sudo apt install -y eza

gpg キー用のフォルダが /etc/apt/trusted.gpg.d となり keyrings とは異なっていたので変更。

gpg キーファイル、ソースストのファイル名も eza.gpg と eza.list にそれぞれに変更した。もとの gierens という名前だと後で見たとき恐らく何ために入れたかわからなくなりそうなので変えた。
これは好みの問題で、そのままで変える必要はない。

$ wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/eza.gpg
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/eza.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/eza.list
$ sudo chmod 644 /etc/apt/trusted.gpg.d/eza.gpg /etc/apt/sources.list.d/eza.list
$ sudo apt update
$ sudo apt install -y eza

eza という名前を忘れるのでエイリアスを ~/.barshrc や~/ .bash_aliases に書き込む。書き込んだら . (source コマンド) で反映させる。

$ alias ls='eza'
$ . ~/.bashrc

参考

eza/INSTALL.md at main · eza-community/eza

LM22: Mozc の辞書ツールにキーボードショートカットを割り当てて、ユーザ辞書を追加した

最近、日本語変換で積極的に単語登録するようにしている。特別な方法ではなくよくある「よろ」で「よろしくお願いします」に変換するような短縮を登録している。

で、辞書に単語登録するのが面倒な理由の一つが辞書ツールや登録ツールを呼び出すのに IME のアイコンから右クリックしてメニューを選択するというアクションがいちいち面倒なためだと思う。
この面倒さは、Linux のデスクトップでも Windows でもほとんど変わらない。しかの最終的に変換できてしまうものだからなんとなく後回しにしがち。

でだ、辞書ツールのコマンドラインがわからないので Archi Linux の Wiki を調べるとユーザー辞書を追加したほうがいいよ的なことが書いてあるのであわせて実施してみた

辞書ツールにショートカット割り当て

LM22 (Linux Mint) のCinnamon で行っているので Ubuntu とか他のデスクトップ環境では若干異なると思うがほぼ同じようなことはできるはずだ。

辞書ツールのコマンド

Mozc – ArchWiki によると設定ツール、辞書ツール、単語登録というのは mozc_tool という一つのファイルで引数を切り替えることで使い分けているようです。

実際のコマンドはこんな感じらしい。

設定ツール:
/usr/lib/mozc/mozc_tool –mode=config_dialog

辞書ツール
/usr/lib/mozc/mozc_tool –mode=dictionary_tool

単語登録
/usr/lib/mozc/mozc_tool –mode=word_register_dialog

今回は、ラウンチャアイコンは作成せずに、辞書ツールに直接キーボード・ショートカットを割り当てることにした。

  1. 設定アプリからキーボードを開く
  2. ショートカットタブを選択する
  3. カスタムショートカットを選択
  4. [Add custom shortcut] ボタンをクリック
  5. Name: を適当に決める
  6. Command: に /usr/lib/mozc/mozc_tool –mode=dictionary_tool を指定する
  7. 登録して、ダイアログを閉じる。
  8. 下段のキーボードバインディングで unassigned の行をダブルクリックする。
  9. 割り当てたいキーボードショートカットを押す。

自分は、Shift + Ctrl + D をショートカットに割り当てましたが、このへんは好みで変えてほしい。
また、個人的な好みでダブリ登録したくないので辞書ツールを起動しているが、単語登録でもいいと思う。

ユーザー辞書登録

ネット上では Mozc UT ってのを使うっていう記事がたくさん見つかりますがこれ、Mozc そのものをビルドしなきゃいけないんですね。ちょっと面倒なので、他の方法でいくことにした。

Mozc UT Dictionaries

utuhiro78/merge-ut-dictionaries: Merge multiple Mozc UT dictionaries into one and modify the costs.

だいぶ古い記事になるのですが、

Google日本語入力 強化辞書の構成を変えてみた。 – 黒MacBookが好き の辞書をインポートして使っている。

Mozcに辞書を追加した: arcadia’s blog のやり方が参考になるかもしれない。

考えるのも面倒だったので、自分の場合はそのまま辞書ツールでインポートした。

英和と和英辞書はインポートしなくてもよかったかもしれない。

Remmina が mdns で名前解決できるようにした

最近は、いちいち VNC クライアントをインストールがめんどうなので LM22 にはじめから入っている Remmina を 使っている。

がしかし、Remmina だと mdns による名前解決が使えなくて困っていた。

Remmina 自身は flatpak でインストールしていたが、D-Bus のパーミッション (socket=system-bus、socket=session-bus) を許可しても現象に変化がなかった。

Claude に聞いてみると /etc/nsswitch.conf を書き換えろってことでやってみた。

変更前:
hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname mymachines

変更後:
hosts: files mdns4_minimal mdns4 [NOTFOUND=return] dns myhostname mymachines

[NOTFOUND] の前に mdns4 を追加すると Remmina でも名前解決できるようになった。

edk2 メモリマップをファイルに保存 #2

前回は、メモリマップをテキストファイルに保存しました。

ReadFile() と WriteFile() は使っていないので別ファイルに逃がすことにします。プログラムの動作は変わりません。それに伴い Include フォルダと Library フォルダが増えています。

ファイル構成

  • edk2 フォルダ
    • Hello7Pkg フォルダ
      • Hello7.inf
      • Hello7Pkg.dec
      • Hello7Pkg.dsc
      • Hello7.c
    • Include フォルダ
      • FileLib.h
    • Library フォルダ
      • FileLib.c
      • FileLib.inf
  • mnt フォルダ
  • disk.img

Hello6.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Library/PrintLib.h>

#define MEMORY_MAP_BUFFER_PADDING 2

typedef struct {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * EFI_PAGE_SIZE - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
SaveMemoryMap(
    IN CONST MemoryMap* map,
    IN CONST EFI_FILE_PROTOCOL* root,
    IN CONST CHAR16* fileName
)
{
    if (map == NULL || root == NULL || fileName ==NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL* File;
    UINTN WriteSize;

    Status = root->Open(
        (EFI_FILE_PROTOCOL*)root,
        &File,
        (CHAR16*)fileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create memory map file: %r\n", Status);
        return Status;
    }

    // CSV header
    CHAR8* Header = "Index, Type, PhysicalStart, NumberOfPages, Attribute\r\n";
    WriteSize = AsciiStrLen(Header);
    Status = File->Write(File, &WriteSize, Header);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write header%r\n", Status);
        File->Close(File);
        return Status;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    for (UINTN i = 0; i < Entries; i++) {
        
        // setup string
        CHAR8 Line[256];

        WriteSize = AsciiSPrint(Line, sizeof(Line),
            "%d, %d, %-ls, 0x%08lx, %ld, 0x%lx\r\n",
            i,
            Desc->Type,
            GetMemoryTypeString(Desc->Type),
            Desc->PhysicalStart,
            Desc->NumberOfPages,
            Desc->Attribute
        );

        // write string to file
        Status = File->Write(File, &WriteSize, Line);
        if (EFI_ERROR(Status)) {
            Print(L"fail: to write entry: %d%r\n", Status);
            File->Close(File);
            return Status;
        }

        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }

    // add Summary Info.
    CHAR8 Summary[256];
    WriteSize = AsciiSPrint(Summary, sizeof(Summary),
        "\r\nMemory Map Summary\r\nEntries: %d\r\nDescriptor Size: %d\r\n",
        Entries,
        map->DescriptorSize
    );

    Status = File->Write(File, &WriteSize, Summary);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write MemMap summary%r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + MEMORY_MAP_BUFFER_PADDING * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FileSystem;
    EFI_FILE_PROTOCOL* Root;
    Status = SystemTable->BootServices->LocateProtocol(
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        (VOID**)&FileSystem
    );
    if (EFI_ERROR(Status)) {
        Print(L"faile: locate file system protocol\n");
        FreeMemoryMap(&Map);
        return Status;
    }

    Status = FileSystem->OpenVolume(FileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"faile: open root directory\n");
        FreeMemoryMap(&Map);
        return Status;
    }    

    Status = SaveMemoryMap(&Map, Root, L"\\memmap.csv");
    if (EFI_ERROR(Status)) {
        Print(L"%r\n");
        Root->Close(Root);
        FreeMemoryMap(&Map);
        return Status;
    }

    Print(L"\nMemMap ha benn saved to \\memmap.csv\n");


    Root->Close(Root);
    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

WriteFile() とReadFile() を削除しました。

Hello7.inf は FileLib を追加していますが、実際には呼び出していないのでコメントアウトしています。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello7
FILE_GUID       = 21E44BD4-AC20-4D08-9265-87CA1AA79BCC
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello7.c

[Guids]
# gEfiFileInfoGuid
# gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello7Pkg/Hello7Pkg.dec

[LibraryClasses]
UefiApplicationEntryPoint
UefiLib
MemoryAllocationLib
PrintLib
# FileLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello7Pkg.dec にも FileLib.h をインクルードすることを知らせています。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello7Pkg
PACKAGE_GUID        = 092DE508-84C6-4FA3-884C-1C5E702CB38E
PACKAGE_VERSION     = 0.0.1

[Includes]
Include

[Guids]

[LibraryClasses]
FileLib|Include/FileLib.h

[Protocols]

[PcdsFixedAtBuild]

Hello7Pkg.dsc ファイルにも FileLib を使うことを知らせています。

[Defines]
PLATFORM_NAME           = Hello7Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello7Pkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS           = DEBUG | RELEASE | NOOPT
SKUID_IDENTIFIER        = DEFAULT

[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf


BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf

FileLib|Hello7Pkg/Library/FileLib/FileLib.inf

[Components]
Hello7Pkg/Hello7.inf

追加した Include フォルダには、FileLib.h を置きます。まあ普通の C のヘッダーです。

#ifndef FILE_LIB_H_
#define FILE_LIB_H_

#include <Uefi.h>
#include <Protocol/SimpleFileSystem.h>

EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    IN CONST CHAR8* Data
    );

EFI_STATUS
ReadFile(
    IN CONST EFI_FILE_PROTOCOL* Root,
    IN CONST CHAR16* FileName,
    OUT CHAR8* Buffer,
    IN UINTN BufferSize,
    OUT UINTN* ReadSize
    );

#endif

Library フォルダには 2 つファイルを追加します。まずは FileLib.c

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Protocol/SimpleFileSystem.h>
#include "FileLib.h"

EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR8 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create file\n");
        return Status;
    }

    Status = File->Write(File, &WriteSize, (VOID*)Data);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write file\n");
        File->Close(File);
        return Status;
    }
    
    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR8 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

    Status = Root->Open(
        Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to open file for reading\n");
        return Status;
    }

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

次は Library/FileLib.inc です。 Claude がいうには、こっちにも Hello7Pkg/Hello7Pkg.inf を入れろとあるが、いらないようなきもする

[Defines]
INF_VERSION                    = 0x00010005
BASE_NAME                      = FileLib
FILE_GUID                      = F0022AC1-CEE2-495D-AA5E-5C620186A083
MODULE_TYPE                    = BASE
VERSION_STRING                 = 1.0
LIBRARY_CLASS                  = FileLib

[Sources]
FileLib.c

[Packages]
MdePkg/MdePkg.dec

[LibraryClasses]
UefiLib
PrintLib

ビルドはいつもと同じ感じです。

上手く動くと、test.txt がイメージファイルのルートディレクトリに作成され、QEMU の画面に test.txt から読み込んだ Hello file system\n が表示されるはず。

test.txt が存在する状態で実行してもファイルを作り直しているのかファイルのタイムスタンプは更新されて問題なく動作している。

$ cd edk2
$ build -p Hello7Pkg/Hello7Pkg.dsc
$ cd ..

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello7Pkg/DEBUG_CLANGDWARF/X64/Hello7.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# QEMU で実行
$ qemu-system-x86_64 \
		-drive if=pflash,format=raw,readonly=on,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd \
		-drive if=pflash,format=raw,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd \
		-drive format=raw,file=disk.img

ソースもきれいになったことだし、カーネルロードに取り掛かろう

edk2 メモリマップをファイルに保存

前回は、メモリマップを画面にダンプしたので今回は、メモリマップをテキストファイルに保存します。

Mikan 本にならい、メモリマップ用の構造体を作り、UEFI コンソールにメモリマップを表示してみます。

ファイル構成

  • edk2 フォルダ
    • Hello6Pkg フォルダ
      • Hello6.inf
      • Hello6Pkg.dec
      • Hello6Pkg.dsc
      • Hello6.c
  • mnt フォルダ
  • disk.img

Hello6.c は前回までファイル読み書き用の関数は後で使う予定なのでそのまま残してメモリマップまわりの処理を追加していく。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/MemoryAllocationLib.h>
#include <Guid/FileInfo.h>
#include <Guid/FileSystemInfo.h>
#include <Library/PrintLib.h>

#define MEMORY_MAP_BUFFER_PADDING 2

typedef struct {
    UINTN BufferSize;
    VOID* Buffer;
    UINTN MapSize;
    UINTN MapKey;
    UINTN DescriptorSize;
    UINT32 DescriptorVersion;
} MemoryMap;

VOID
InitializeMemoryMap(
    OUT MemoryMap* map
)
{
    if (map == NULL) return;

    map->BufferSize = 0;
    map->Buffer = NULL;
    map->MapSize = 0;
    map->MapKey = 0;
    map->DescriptorSize = 0;
    map->DescriptorVersion =0;
}

VOID
FreeMemoryMap(
    IN OUT MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) return;

    FreePool(map->Buffer);
    map->Buffer = NULL;
    map->BufferSize = 0;
}

const CHAR16*
GetMemoryTypeString(
    IN EFI_MEMORY_TYPE type
)
{
    switch (type) {
        case EfiReservedMemoryType:     return L"Reserved";
        case EfiLoaderCode:             return L"LoaderCode";
        case EfiLoaderData:             return L"LoaderData";
        case EfiBootServicesCode:       return L"BootCode";
        case EfiBootServicesData:       return L"BootData";
        case EfiRuntimeServicesCode:    return L"RuntimeCode";
        case EfiRuntimeServicesData:    return L"RuntimeData";
        case EfiConventionalMemory:     return L"Conventional";
        case EfiUnusableMemory:         return L"Unusable";
        case EfiACPIReclaimMemory:      return L"ACPIReclaim";
        case EfiACPIMemoryNVS:          return L"ACPINVS";
        case EfiMemoryMappedIO:         return L"MMIO";
        case EfiMemoryMappedIOPortSpace: return L"MMIOPort";
        case EfiPalCode:                return L"PalCode";
        default:                        return L"Unknown";
    }
}


VOID
PrintMemoryMapEntry(
    IN UINTN index,
    IN CONST EFI_MEMORY_DESCRIPTOR* desc
)
{
    Print(L"%03d: Type=%s  Phys=0x%016lx-0x%016lx  Pages=%ld  Attr=0x%016lx\n",
        index,
        GetMemoryTypeString(desc->Type),
        desc->PhysicalStart,
        desc->PhysicalStart + desc->NumberOfPages * EFI_PAGE_SIZE - 1,
        desc->NumberOfPages,
        desc->Attribute
    );
}

VOID
PrintMemoryMap(
    IN CONST  MemoryMap* map
)
{
    if (map == NULL || map->Buffer == NULL) {
        Print(L"Error: invalid memory map\n");
        return;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    Print(L"Memory Map: %d entries\n", Entries);
    Print(L"DescriptorSize: %d\n",map->DescriptorSize);

    for (UINTN i = 0; i < Entries; i++) {
        PrintMemoryMapEntry(i, Desc);
        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }
}

EFI_STATUS
SaveMemoryMap(
    IN CONST MemoryMap* map,
    IN CONST EFI_FILE_PROTOCOL* root,
    IN CONST CHAR16* fileName
)
{
    if (map == NULL || root == NULL || fileName ==NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL* File;
    UINTN WriteSize;

    Status = root->Open(
        (EFI_FILE_PROTOCOL*)root,
        &File,
        (CHAR16*)fileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create memory map file: %r\n", Status);
        return Status;
    }

    // CSV header
    CHAR8* Header = "Index, Type, PhysicalStart, NumberOfPages, Attribute\r\n";
    WriteSize = AsciiStrLen(Header);
    Status = File->Write(File, &WriteSize, Header);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write header%r\n", Status);
        File->Close(File);
        return Status;
    }

    EFI_MEMORY_DESCRIPTOR* Desc = (EFI_MEMORY_DESCRIPTOR*)map->Buffer;
    UINTN Entries = map->MapSize / map->DescriptorSize;

    for (UINTN i = 0; i < Entries; i++) {
        
        // setup string
        CHAR8 Line[256];

        WriteSize = AsciiSPrint(Line, sizeof(Line),
            "%d, %d, %-ls, 0x%08lx, %ld, 0x%lx\r\n",
            i,
            Desc->Type,
            GetMemoryTypeString(Desc->Type),
            Desc->PhysicalStart,
            Desc->NumberOfPages,
            Desc->Attribute
        );

        // write string to file
        Status = File->Write(File, &WriteSize, Line);
        if (EFI_ERROR(Status)) {
            Print(L"fail: to write entry: %d%r\n", Status);
            File->Close(File);
            return Status;
        }

        Desc = NEXT_MEMORY_DESCRIPTOR(Desc, map->DescriptorSize);
    }

    // add Summary Info.
    CHAR8 Summary[256];
    WriteSize = AsciiSPrint(Summary, sizeof(Summary),
        "\r\nMemory Map Summary\r\nEntries: %d\r\nDescriptor Size: %d\r\n",
        Entries,
        map->DescriptorSize
    );

    Status = File->Write(File, &WriteSize, Summary);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write MemMap summary%r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
GetMemoryMap(MemoryMap* map) {
    EFI_STATUS Status;

    if (map == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    // 初期サイズ 0 でメモリマップの大きさを取得
    map->MapSize = 0;
    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (Status != EFI_BUFFER_TOO_SMALL) {
        Print(L"Error: Unexpected GetMemory Status: %r\n", Status);
        return Status;
    }

    map->BufferSize = map->MapSize + MEMORY_MAP_BUFFER_PADDING * map->DescriptorSize;

    if (map->Buffer != NULL) {
        FreePool(map->Buffer);
    }

    map->Buffer = AllocatePool(map->BufferSize);
    if (map->Buffer == NULL) {
        Print(L"fail: to allocate memory for map buffer: %r\n", Status);
        return EFI_OUT_OF_RESOURCES;
    }

    Status = gBS->GetMemoryMap(
        &map->MapSize,
        map->Buffer,
        &map->MapKey,
        &map->DescriptorSize,
        &map->DescriptorVersion
    );

    if (EFI_ERROR(Status)) {
        FreePool(map->Buffer);
        map->Buffer = NULL;
        Print(L"fail: to get memory map: %r\n", Status);
        return Status;
    }

    return EFI_SUCCESS;
}



EFI_STATUS
WriteFile(
    IN CONST EFI_FILE_PROTOCOL *Root,
    IN CONST CHAR16 *FileName,
    IN CONST CHAR8 *Data
)
{
    if (Root == NULL || FileName == NULL || Data == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;
    UINTN WriteSize = StrLen(Data) * sizeof(CHAR16);

    Status = Root->Open(
        (EFI_FILE_PROTOCOL*)Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_CREATE | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to create file\n");
        return Status;
    }

    Status = File->Write(File, &WriteSize, (VOID*)Data);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to write file\n");
        File->Close(File);
        return Status;
    }
    
    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
ReadFile(
    IN EFI_FILE_PROTOCOL *Root,
    IN CHAR16 *FileName,
    OUT CHAR8 *Buffer,
    IN UINTN BufferSize,
    OUT UINTN *ReadSize
)
{
    if (Root == NULL || FileName == NULL || Buffer == NULL || 
        BufferSize == 0 || ReadSize == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    EFI_STATUS Status;
    EFI_FILE_PROTOCOL *File;

    Status = Root->Open(
        Root,
        &File,
        (CHAR16*)FileName,
        EFI_FILE_MODE_READ,
        0
    );
    if (EFI_ERROR(Status)) {
        Print(L"fail: to open file for reading\n");
        return Status;
    }

    *ReadSize = BufferSize;
    Status = File->Read(File, ReadSize, Buffer);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to read file: %r\n", Status);
        File->Close(File);
        return Status;
    }

    File->Close(File);
    return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
UefiMain(
    IN EFI_HANDLE ImageHandle,
    IN EFI_SYSTEM_TABLE *SystemTable
)
{
    EFI_STATUS Status;
    MemoryMap Map;

    InitializeMemoryMap(&Map);

    Status = GetMemoryMap(&Map);
    if (EFI_ERROR(Status)) {
        Print(L"fail: to get memory map %r\n", Status);
        return Status;
    }

    PrintMemoryMap(&Map);

    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* FileSystem;
    EFI_FILE_PROTOCOL* Root;
    Status = SystemTable->BootServices->LocateProtocol(
        &gEfiSimpleFileSystemProtocolGuid,
        NULL,
        (VOID**)&FileSystem
    );
    if (EFI_ERROR(Status)) {
        Print(L"faile: locate file system protocol\n");
        FreeMemoryMap(&Map);
        return Status;
    }

    Status = FileSystem->OpenVolume(FileSystem, &Root);
    if (EFI_ERROR(Status)) {
        Print(L"faile: open root directory\n");
        FreeMemoryMap(&Map);
        return Status;
    }    

    Status = SaveMemoryMap(&Map, Root, L"\\memmap.csv");
    if (EFI_ERROR(Status)) {
        Print(L"%r\n");
        Root->Close(Root);
        FreeMemoryMap(&Map);
        return Status;
    }

    Print(L"\nMemMap ha benn saved to \\memmap.csv\n");


    Root->Close(Root);
    FreeMemoryMap(&Map);

    return EFI_SUCCESS;
}

Hello6.inf に、 PrintLib を追加した。PrintLib の中に AsciiSPrint() が含まれているらしい。

[Defines]
INF_VERSION     = 0x00010005
BASE_NAME       = Hello6
FILE_GUID       = A522DAB7-30AE-4F3D-85D1-92D1C24BEF30
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.0.1
ENTRY_POINT     = UefiMain

[Sources]
Hello6.c

[Guids]
gEfiFileInfoGuid
gEfiFileSystemInfoGuid

[Packages]
MdePkg/MdePkg.dec
Hello6Pkg/Hello6Pkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
MemoryAllocationLib
PrintLib

[Protocols]
gEfiSimpleFileSystemProtocolGuid

Hello6Pkg.dec はほとんどわからない。

[Defines]
DEC_SPECIFICATION   = 0x00010005
PACKAGE_NAME        = Hello6Pkg
PACKAGE_GUID        = A1D6D642-D269-44DF-B225-5FFA7AB8E958
PACKAGE_VERSION     = 0.0.1

[Includes]

[Guids]

[Protocols]

[PcdsFixedAtBuild]

Hello6Pkg.dsc ファイルは変更はありません。ということは、以前のものは、PrintLib 行はいらなかったのかもしれない。

[Defines]
PLATFORM_NAME           = Hello6Pkg
PLATFORM_GUID           = DA50351D-D7B7-45DD-911C-B54BAB54F702
PLATFORM_VERSION        = 0.0.1
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/Hello6Pkg
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS           = DEBUG | RELEASE | NOOPT
SKUID_IDENTIFIER        = DEFAULT

[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf


BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf

[Components]
Hello6Pkg/Hello6.inf

ビルドはいつもと同じ感じです。

上手く動くと、test.txt がイメージファイルのルートディレクトリに作成され、QEMU の画面に test.txt から読み込んだ Hello file system\n が表示されるはず。

test.txt が存在する状態で実行してもファイルを作り直しているのかファイルのタイムスタンプは更新されて問題なく動作している。

$ cd edk2
$ build -p Hello6Pkg/Hello6Pkg.dsc
$ cd ..

# イメージディスクに書き込む
$ sudo mount -o loop,uid=$(id -u),gid=$(id -u) disk.img ./mnt
$ cp ./edk2/Build/Hello6Pkg/DEBUG_CLANGDWARF/X64/Hello6.efi ./mnt/EFI/BOOT
$ sudo umount ./mnt

# QEMU で実行
$ qemu-system-x86_64 \
		-drive if=pflash,format=raw,readonly=on,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_CODE.fd \
		-drive if=pflash,format=raw,file=edk2/Build/OvmfX64/DEBUG_GCC/FV/OVMF_VARS.fd \
		-drive format=raw,file=disk.img

次こそカーネルのロードにに進める。

memmap.csv は fs0: の ルートに作成される。上手く行けばこんな感じになる。QEMU を同じように起動すれば恐らく同じようなメモリマップになるはず。

Index, Type, PhysicalStart, NumberOfPages, Attribute
0, 7, Conventional, 0x00000000, 160, 0xF
1, 7, Conventional, 0x00100000, 1792, 0xF
2, 10, ACPINVS, 0x00800000, 8, 0xF
3, 7, Conventional, 0x00808000, 3, 0xF
4, 10, ACPINVS, 0x0080B000, 1, 0xF
5, 7, Conventional, 0x0080C000, 5, 0xF
6, 10, ACPINVS, 0x00811000, 239, 0xF
7, 4, BootData, 0x00900000, 3712, 0xF
8, 7, Conventional, 0x01780000, 9196, 0xF
9, 4, BootData, 0x03B6C000, 32, 0xF
10, 7, Conventional, 0x03B8C000, 9325, 0xF
11, 1, LoaderCode, 0x05FF9000, 227, 0xF
12, 7, Conventional, 0x060DC000, 90, 0xF
13, 4, BootData, 0x06136000, 31, 0xF
14, 7, Conventional, 0x06155000, 15, 0xF
15, 1, LoaderCode, 0x06164000, 3, 0xF
16, 7, Conventional, 0x06167000, 3, 0xF
17, 4, BootData, 0x0616A000, 1569, 0xF
18, 3, BootCode, 0x0678B000, 180, 0xF
19, 4, BootData, 0x0683F000, 108, 0xF
20, 3, BootCode, 0x068AB000, 30, 0xF
21, 4, BootData, 0x068C9000, 1, 0xF
22, 3, BootCode, 0x068CA000, 22, 0xF
23, 4, BootData, 0x068E0000, 2, 0xF
24, 3, BootCode, 0x068E2000, 21, 0xF
25, 4, BootData, 0x068F7000, 4, 0xF
26, 3, BootCode, 0x068FB000, 30, 0xF
27, 4, BootData, 0x06919000, 5, 0xF
28, 3, BootCode, 0x0691E000, 47, 0xF
29, 4, BootData, 0x0694D000, 6, 0xF
30, 3, BootCode, 0x06953000, 40, 0xF
31, 4, BootData, 0x0697B000, 1, 0xF
32, 3, BootCode, 0x0697C000, 21, 0xF
33, 4, BootData, 0x06991000, 8, 0xF
34, 3, BootCode, 0x06999000, 19, 0xF
35, 4, BootData, 0x069AC000, 1, 0xF
36, 3, BootCode, 0x069AD000, 43, 0xF
37, 4, BootData, 0x069D8000, 5, 0xF
38, 3, BootCode, 0x069DD000, 35, 0xF
39, 4, BootData, 0x06A00000, 514, 0xF
40, 3, BootCode, 0x06C02000, 3, 0xF
41, 4, BootData, 0x06C05000, 3, 0xF
42, 3, BootCode, 0x06C08000, 11, 0xF
43, 4, BootData, 0x06C13000, 1, 0xF
44, 3, BootCode, 0x06C14000, 4, 0xF
45, 4, BootData, 0x06C18000, 6, 0xF
46, 3, BootCode, 0x06C1E000, 47, 0xF
47, 4, BootData, 0x06C4D000, 9, 0xF
48, 3, BootCode, 0x06C56000, 33, 0xF
49, 4, BootData, 0x06C77000, 4, 0xF
50, 3, BootCode, 0x06C7B000, 28, 0xF
51, 4, BootData, 0x06C97000, 5, 0xF
52, 3, BootCode, 0x06C9C000, 7, 0xF
53, 4, BootData, 0x06CA3000, 3, 0xF
54, 3, BootCode, 0x06CA6000, 36, 0xF
55, 4, BootData, 0x06CCA000, 3, 0xF
56, 3, BootCode, 0x06CCD000, 15, 0xF
57, 4, BootData, 0x06CDC000, 2, 0xF
58, 3, BootCode, 0x06CDE000, 3, 0xF
59, 4, BootData, 0x06CE1000, 3, 0xF
60, 3, BootCode, 0x06CE4000, 38, 0xF
61, 4, BootData, 0x06D0A000, 5, 0xF
62, 3, BootCode, 0x06D0F000, 2, 0xF
63, 4, BootData, 0x06D11000, 1042, 0xF
64, 3, BootCode, 0x07123000, 6, 0xF
65, 4, BootData, 0x07129000, 2, 0xF
66, 3, BootCode, 0x0712B000, 10, 0xF
67, 4, BootData, 0x07135000, 6, 0xF
68, 3, BootCode, 0x0713B000, 23, 0xF
69, 4, BootData, 0x07152000, 923, 0xF
70, 6, RuntimeData, 0x074ED000, 256, 0x800000000000000F
71, 5, RuntimeCode, 0x075ED000, 256, 0x800000000000000F
72, 0, Reserved, 0x076ED000, 128, 0xF
73, 9, ACPIReclaim, 0x0776D000, 18, 0xF
74, 10, ACPINVS, 0x0777F000, 128, 0xF
75, 4, BootData, 0x077FF000, 1537, 0xF
76, 7, Conventional, 0x07E00000, 96, 0xF
77, 4, BootData, 0x07E60000, 32, 0xF
78, 3, BootCode, 0x07E80000, 47, 0xF
79, 0, Reserved, 0x07EAF000, 4, 0xF
80, 10, ACPINVS, 0x07EB3000, 2, 0xF
81, 3, BootCode, 0x07EB5000, 1, 0xF
82, 4, BootData, 0x07EB6000, 17, 0xF
83, 3, BootCode, 0x07EC7000, 45, 0xF
84, 6, RuntimeData, 0x07EF4000, 132, 0x800000000000000F
85, 10, ACPINVS, 0x07F78000, 136, 0xF
86, 11, MMIO, 0xFFC00000, 1024, 0x8000000000000001
87, 0, Reserved, 0xFD00000000, 3145728, 0x0

Memory Map Summary
Entries: 88
Descriptor Size: 48