アームの指示。 ARMプロセッサ命令セットの研究。 条件付きコマンドの実行

みなさん、こんにちは!
私は職業別のJavaプログラマーです。 過去数か月の作業で、Android NDKの開発に慣れ、それに応じてCでネイティブアプリケーションを作成することに慣れました。その後、Linuxライブラリの最適化の問題に直面しました。 多くの場合、ARM向けに完全に最適化されておらず、プロセッサの負荷が高いことが判明しました。 以前は、実際にはアセンブリ言語でプログラミングしていなかったので、最初はこの言語を学び始めるのは困難でしたが、それでも試してみることにしました。 この記事は、いわば初心者から初心者まで書かれています。 私はすでに学んだ基本を説明しようとします、誰かがこれに興味を持ってくれることを願っています。 また、専門家から建設的な批判を受けていただければ幸いです。

序章
それで、最初に、ARMが何であるかを理解しましょう。 ウィキペディアはこの定義を与えています:

ARMアーキテクチャ(高度なRISCマシン、Acorn RISCマシン、高度なRISCマシン)は、ARMLimitedによって開発されたライセンス付きの32ビットおよび64ビットマイクロプロセッサコアのファミリです。 同社は、カーネルとそのためのツール(コンパイラ、デバッグツールなど)の開発に専念しており、サードパーティメーカーにアーキテクチャをライセンス供与することで収益を上げています。

誰もが知らない場合、今ではほとんどのモバイルデバイスで、タブレットはこのプロセッサアーキテクチャで正確に開発されています。 このファミリの主な利点は、消費電力が少ないことです。これにより、さまざまな組み込みシステムで頻繁に使用されます。 アーキテクチャは時間とともに進化し、ARMv7 3プロファイルが定義されて以来、アプリケーションの場合は「A」(アプリケーション)、リアルタイムの場合は「R」(リアルタイム)、マイクロコントローラーの場合は「M」(マイクロコントローラー)です。 この技術の開発の歴史やその他の興味深いデータは、ウィキペディアまたはインターネットでグーグルで読むことができます。 ARMはさまざまな操作モードをサポートしています(ThumbとARMに加えて、ARMとThumbを組み合わせたThumb-2が最近登場しました)。 この記事では、32ビット命令セットが実行される実際のARMモードについて検討します。

各ARMプロセッサは、次のブロックから構築されています。

  • 37個のレジスタ(開発中に表示されるのは17個のみ)
  • 算術論理演算装置(ALU)-算術および論理タスクを実行します
  • バレルシフタ-データのブロックを特定のビット数だけ移動するように設計されたデバイス
  • CP15は、ARMコプロセッサーを制御する特別なシステムです。
  • 命令デコーダー-命令の一連のマイクロオペレーションへの変換を処理します
これらはすべてARMの一部ではありませんが、プロセッサの構築のジャングルを掘り下げることは、この記事の範囲を超えています。
パイプラインの実行
ARMプロセッサは3ステージパイプラインを使用します(ARM8以降、5ステージパイプラインが実装されました)。 例としてARM7TDMIプロセッサを使用した単純なパイプラインを見てみましょう。 各命令の実行は、次の3つのステップで構成されます。

1.サンプリング段階(F)
この時点で、命令はRAMからプロセッサパイプラインに流れます。
2.デコード段階(D)
命令はデコードされ、そのタイプが認識されます。
3.実行段階(E)
データはALUに入り、実行され、結果の値が指定されたレジスタに書き込まれます。

ただし、開発時には、ロード(LDR)やストアなど、複数の実行サイクルを使用する命令があることに注意する必要があります。 この場合、実行ステージ(E)はステージ(E1、E2、E3 ...)に分割されます。

条件付き実行
ARMアセンブラの最も重要な機能の1つは、条件付き実行です。 各命令は条件付きで実行でき、これにはサフィックスが使用されます。 命令の名前に接尾辞が追加されている場合は、実行する前にパラメーターがチェックされます。 パラメータが条件に一致しない場合、ステートメントは実行されません。 接尾辞:
MI-負の数
PL-正またはゼロ
AL-常に命令を実行します
さらに多くの条件付き接尾辞があります。 公式ドキュメントの残りのサフィックスと例を読んでください:ARMドキュメント
さて、検討する時が来ました...
ARMアセンブラ構文の基本
以前にアセンブラを使用したことがある人にとっては、この点は実際にはスキップできます。 他の皆さんのために、この言語での作業の基本について説明します。 したがって、すべてのアセンブリ言語プログラムは命令で構成されています。 命令は次のように作成されます。
(ラベル)(命令|オペランド)(@コメント)
ラベルはオプションのパラメータです。 命令は、プロセッサへの命令の直接のニーモニックです。 基本的な手順とその使用法については、後で説明します。 オペランド-定数、レジスタアドレス、RAM内のアドレス。 コメントは、プログラムの実行に影響を与えないオプションのパラメーターです。
レジスタ名
次のレジスタ名を使用できます。
1.r0-r15

3.v1-v8(可変レジスタ、r4からr11)

4.sbおよびSB(静的レジスタ、r9)

5.slおよびSL(r10)

6.fpおよびFP(r11)

7.ipおよびIP(r12)

8.spおよびSP(r13)

9.lrおよびLR(r14)

10.pcおよびPC(プログラムカウンター、r15)。

変数と定数
ARMアセンブラでは、(実際には)他のプログラミング言語と同様に、変数と定数を使用できます。 それらは次のタイプに分けられます:
  • 数値
  • 頭の体操
数値変数は次のように初期化されます。
SETA 100; 値が100の数値変数「a」が作成されます。
文字列変数:
即興SETS「リテラル」; 値「literal」を使用して、improb変数が作成されます。 注意! 変数値は5120文字を超えることはできません。
ブール変数は、それぞれ値TRUEとFALSEを使用します。
ARMアセンブラ命令の例
この表では、さらなる開発に必要となる基本的な指示を集めました(最も基本的な段階で:):

基本的な手順を実行するために、いくつかの簡単な例を書いてみましょう。ただし、最初にarmツールチェーンが必要です。 私はLinuxで作業しているので、frank.harvard.edu / 〜coldwell / toolchain(arm-unknown-linux-gnuツールチェーン)を選択しました。 他のLinuxプログラムと同じように、ナシを殻から取り出すのと同じくらい簡単にインストールできます。 私の場合(Russian Fedora)は、サイトからrpmパッケージをインストールするだけで済みました。
次に、最も簡単な例を作成します。 プログラムは完全に役に立たないでしょう、しかし主なことはそれが働くということです:)これが私があなたに提供するコードです:
start:@プログラムの開始を示すオプションの行mov r0、#3 @値3をレジスタr0にロードmovr1、#2 @レジスタr1でも同じことを行い、値2でr2、r1を追加します。 r0 @ r0とr1の値を追加し、r2に答えを書き込みますmul r3、r1、r0 @レジスタr1の値にレジスタr0の値を掛け、r3に答えを書き込みますstop:b stop @ Lineプログラム終了の
プログラムをコンパイルして、.binファイルを取得します。
/ usr / arm / bin / arm-unknown-linux-gnu-as -o arm.o arm.s / usr / arm / bin / arm-unknown-linux-gnu-ld -Ttext = 0x0 -oarm。 elf arm .o / usr / arm / bin / arm-unknown-linux-gnu-objcopy -O binary arm.elf arm.bin
(コードはarm.sファイルにあり、私の場合のツールチェーンは/ usr / arm / bin /ディレクトリにあります)
すべてがうまくいけば、arm.s(実際のコード)、arm.o、arm.elf、arm.bin(実際の実行可能プログラム)の3つのファイルができます。 プログラムの動作をテストするために、独自のアームデバイスを用意する必要はありません。 QEMUをインストールするだけで十分です。 参考のために:

QEMUは、さまざまなプラットフォーム向けの無料のオープンソースハードウェアエミュレーションソフトウェアです。

Intelx86プロセッサとI / Oデバイスのエミュレーションが含まれます。 80386、80486、Pentium、Pentium Pro、AMD64、およびその他のx86互換プロセッサをエミュレートできます。 PowerPC、ARM、MIPS、SPARC、SPARC64、m68k-部分的にのみ。

Syllable、FreeBSD、FreeDOS、Linux、Windows 9x、Windows 2000、Mac OS X、QNX、Androidなどで動作します。

したがって、armをエミュレートするには、qemu-system-armが必要です。 このパッケージはyumに含まれているため、Fedoraを使用している場合は、わざわざコマンドを実行する必要はありません。
yum install qemu-system-arm

次に、ARMエミュレーターを起動して、arm.binプログラムを実行する必要があります。 これを行うには、QEMUのフラッシュメモリとなるflash.binファイルを作成します。 これを行うのは非常に簡単です:
dd if = / dev / zero of = flash.bin bs = 4096 count = 4096 dd if = arm.bin of = flash.bin bs = 4096 conv = notrunc
次に、受信したフラッシュメモリをQEMUにロードします。
qemu-system-arm -M connex -pflash flash.bin -nographic -serial / dev / null
出力には次のようなものが表示されます。

$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial / dev / null
QEMU 0.15.1モニター-詳細については、「help」と入力してください
(qemu)

私たちのarm.binプログラムは4つのレジスタの値を変更する必要があったため、作業の正確さを確認するために、これらの同じレジスタを見てみましょう。 これは非常に簡単なコマンドで実行されます:情報レジスタ
出力には、15個のARMレジスタがすべて表示され、そのうち4個の値が変更されています。 チェック:)レジスタの値は、プログラムの実行後に期待できる値と一致します:
(qemu)情報レジスタR00 = 00000003 R01 = 00000002 R02 = 00000005 R03 = 00000006 R04 = 00000000 R05 = 00000000 R06 = 00000000 R07 = 00000000 R08 = 00000000 R09 = 00000000 R10 = 00000000 R11 = 00000000 R12 = 00000000 R13 = 00000000 R14 = 00000000 R15 = 00000010 PSR = 400001d3 -Z-- A svc32

追伸 この記事では、ARMアセンブラでのプログラミングの基本について説明しました。 楽しんでください! これは、この言語のジャングルをさらに掘り下げて、その言語でプログラムを作成するのに十分です。 すべてがうまくいったら、私は自分が見つけたものについて書き続けます。 エラーが発生した場合は、アセンブラを初めて使用するため、キックしないでください。

Raspberry PiのオペレーティングシステムとしてRaspbianディストリビューションを使用している場合は、as(アセンブリ言語のソースコードをバイナリコードに変換するアセンブラー)とld(結果の実行可能ファイルを作成するリンカー)の2つのユーティリティが必要になります。 どちらのユーティリティもbinutilsソフトウェアパッケージに含まれているため、システムにすでに存在している可能性があります。 もちろん、優れたテキストエディタも必要です。 ソフトウェア開発には常にVimを使用することをお勧めしますが、参入障壁が高いため、Nanoやその他のGUIテキストエディターで問題なく動作します。

始める準備はできましたか? 次のコードをコピーして、myfirst.sファイルに保存します。

グローバル_start_start:mov r7、#4 mov r0、#1 ldr r1、= string mov r2、#stringlen swi 0 mov r7、#1 swi 0 .data string:.ascii "Ciao!\ N" stringlen =。 - ストリング

このプログラムは、文字列「Ciao!」を出力するだけです。 画面上で、x86中央処理装置でのアセンブリ言語の使用に関する記事を読んだことがある場合は、使用されている手順の一部に精通している可能性があります。 しかし、それでも、x86アーキテクチャとARMアーキテクチャの命令には多くの違いがあり、ソースコードの構文についても言えるので、詳細に説明します。

ただし、その前に、指定されたコードをアセンブルし、結果のオブジェクトファイルを実行可能ファイルにリンクするには、次のコマンドを使用する必要があることに注意してください。

As -o myfirst.o myfirst.s && ld -o myfirst myfirst.o

これで、作成したプログラムをコマンド/ Myfirstで実行できます。 実行可能ファイルのサイズが約900バイトと非常に小さいことに気付いたと思います。Cプログラミング言語とputs()関数を使用した場合、バイナリファイルのサイズは約5倍になります。

RaspberryPi用の独自のオペレーティングシステムの構築

x86アセンブリ言語プログラミングシリーズの以前の記事を読んだことがあるなら、Linuxや他のオペレーティングシステムの助けを借りずに画面にメッセージを表示して、最初に独自のオペレーティングシステムを起動した瞬間を覚えているでしょう。 その後、シンプルなコマンドラインインターフェイスとディスクからプログラムをロードして実行するためのメカニズムを追加し、将来のために予約を残して、それを改良しました。 これは非常に興味深いものでしたが、主にBIOSファームウェアの助けを借りて、それほど難しい作業ではありませんでした。画面、キーボード、フロッピーディスクリーダーにアクセスするための簡素化されたインターフェイスを提供しました。

Raspberry Piの場合、便利なBIOS機能を自由に使用できなくなるため、デバイス用のドライバーを自分で開発する必要があります。これは、画面に描画して実装する場合と比較して、それ自体が困難で面白くない作業です。独自のプログラムを実行するためのメカニズム。 同時に、ネットワーク上には、Raspberry Piブートプロセスの初期段階、GPIOピンにアクセスするためのメカニズムの機能などを詳細に説明するいくつかのマニュアルがあります。

そのような最高のドキュメントの1つは、ケンブリッジ大学のBaking Pi(www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/index.html)と呼ばれる論文です。 基本的には、LEDをオンにしたり、画面上のピクセルにアクセスしたり、キーボード入力を取得したりするためのアセンブリ言語の手法を説明する一連のチュートリアルです。 読んでいくと、Raspberry Piのハードウェアについて多くのことを学び、これらのシングルボードコンピューターの元のモデル用に作成されたマニュアルがあるため、A +、B +などのモデルに関連する保証はありません。 、およびPi2。

Cプログラミング言語を使用する場合は、http://tinyurl.com/qa2s9bgにあるValversリソースのドキュメントを参照する必要があります。このドキュメントには、クロスコンパイラを構成し、最も単純なオペレーティングシステムカーネルを構築するプロセスの説明が含まれています。 Raspberry Piで基本的なOSカーネルを構築して実行する方法については、http://wiki.osdev.org/Raspberry_Pi_Bare_Bonesにある便利なOSDevリソースのWikiセクションを参照してください。

前述のように、この場合の最大の問題は、さまざまなハードウェアRaspberry Piデバイス(USBコントローラー、SDカードスロットなど)用のドライバーを開発する必要があることです。 上記のデバイスのコードでさえ、数万行かかる可能性があります。 それでもRaspberryPi用の完全に機能する独自のオペレーティングシステムを開発したい場合は、www.osdev.orgのフォーラムにアクセスして、これらのデバイス用のドライバーを既に開発していないかどうかを確認し、可能であれば、カーネルに適合させてください。オペレーティングシステム、それによってあなたの時間を大幅に節約します。

すべての仕組み

コードの最初の2行は、CPU命令ではなく、アセンブリおよびリンカーディレクティブです。 各プログラムには、_startと呼ばれる明確に定義されたエントリポイントが必要です。この場合、それはコードの最初にありました。 したがって、コードの実行は最初の命令から開始する必要があり、追加のアクションは不要であることをリンカに通知します。

次の命令で、番号4をレジスタr7に入れます。 (これまでアセンブリ言語を使用したことがない場合は、レジスタが中央処理装置に直接配置されたメモリ位置であることに注意してください。最新の中央処理装置のほとんどは、数百万または数十億のRAM位置と比較して少数のレジスタを実装しています。 、ただしレジスタARMアーキテクチャチップは、開発者に多数の汎用レジスタを提供します。開発者は、r0からr15までの名前を持つ最大16個のレジスタを使用でき、これらのレジスタは、アーキテクチャx86。一部のレジスタは特定の時間に特定の目的に使用できます。

したがって、mov命令はx86アーキテクチャの同じ名前の命令と非常に似ていますが、いずれの場合も、次が整数値であることを示す番号4の横のハッシュ記号に注意する必要があります。メモリ内のアドレスではありません。 この場合、Linuxカーネルのwriteシステムコールを使用して行を出力します。 システムコールを使用するには、カーネルにその仕事をさせる前に、レジスタに必要な値を入力する必要があります。 システムコール番号はレジスタr7に配置する必要があり、番号4は書き込みシステムコール番号です。

次のmov命令では、文字列「Ciao!」が書き込まれるファイルの記述子、つまり標準出力ストリームの記述子をレジスタr0に配置します。 この場合、標準出力ストリームが使用されるため、その標準記述子、つまり1がレジスタに配置されます。 次に、ldr命令(「レジスタにロード」命令。アドレスではなくラベルを示す等号に注意)を使用して、出力する文字列のアドレスをレジスタr1に配置する必要があります。 コードの最後、つまりデータセクションで、この文字列をASCII文字のシーケンスの形式で宣言します。 「書き込み」システムコールを正常に使用するには、オペレーティングシステムカーネルに出力文字列の長さを通知する必要があるため、stringlen値をレジスタr2に配置します。 (stringlenの値は、開始アドレスから文字列の終了アドレスを引くことによって計算されます。)

この時点で、すべてのレジスタに必要なデータが入力され、Linuxカーネルに制御を移す準備が整いました。 これを行うには、「ソフトウェア割り込み」を表すswi命令を使用して、OSカーネルスペースにジャンプします(x86アーキテクチャの記事のint命令とほぼ同じ方法で)。 OSカーネルはレジスタr7の内容を調べ、その中の整数値4を見つけて、「したがって、呼び出し元は文字列を出力したい」と結論付けます。 その後、他のレジスタの内容を調べて文字列を出力し、プログラムに制御を戻します。

したがって、画面に「Ciao!」という行が表示されます。その後、プログラムの実行を正しく終了する必要があります。 この問題を解決するには、出口のsyscall番号をレジスタr7に配置してから、ソフトウェア割り込み番号0の命令を呼び出します。 これですべてです。OSカーネルがプログラムの実行を完了し、再びコマンドシェルに移動します。

Vim(左)はアセンブリ言語を書くための優れたテキストエディタです-ARMアーキテクチャの構文強調表示ファイルはhttp://tinyurl.com/psdvjenで入手できます。

助言:アセンブリ言語を使用する場合は、コメントを軽視しないでください。 この記事では、コードが雑誌のページで占めるスペースをできるだけ少なくするために(また、各手順の目的を詳細に説明したため)、コメントをあまり使用しませんでした。 しかし、コードが一見明らかなように見える複雑なプログラムを開発するときは、ARMアーキテクチャのアセンブリ言語の構文を部分的に忘れて、数か月後に開発に戻った後、どのように見えるかを常に考える必要があります。 コードで使用されているすべてのトリックや略語を忘れることができます。そうすると、コードは完全にぎこちないように見えます。 上記に基づいて、現時点であまりにも明白に見えるコメントがある場合でも、できるだけ多くのコメントをコードに追加する必要があります。

リバースエンジニアリング

バイナリファイルをアセンブリ言語に変換することも、場合によっては便利です。 この操作の結果は、通常、読み取り可能なタグ名とコメントのない整形式のコードではありませんが、それでも、コードを使用してアセンブラーによって実行された変換を調べるのに役立ちます。 myfirstバイナリを逆アセンブルするには、次のコマンドを実行するだけです。

Objdump -d myfirst

このコマンドは、バイナリファイルの実行可能セクションを逆アセンブルします(ただし、ASCIIテキストが含まれているためデータセクションは逆アセンブルしません)。 逆アセンブルされたコードを見ると、その中の命令が元のコードの命令と実質的に異ならないことに気付くでしょう。 逆アセンブラは主に、ウイルスや、動作をエミュレートする単純なクローズドソースプログラムなど、バイナリ形式でのみ利用可能なプログラムの動作を調査する必要がある場合に使用されます。 そうすることで、あなたは常に研究中のプログラムの作者によって課された制限について覚えておくべきです! もちろん、プログラムバイナリを逆アセンブルし、結果のコードをプロジェクトコードにコピーすることは悪い考えです。 ただし、結果のコードを使用して、プログラムの原理を学習することもできます。

サブルーチン、ループ、および条件ステートメント

単純なプログラムを設計、アセンブル、およびリンクする方法がわかったので、もう少し複雑なものを見てみましょう。 次のプログラムでは、サブルーチンを使用して行を出力します(これらのおかげで、コードフラグメントを再利用でき、レジスタにデータを入力するために同じタイプの操作を実行する必要がなくなります)。 このプログラムは、ユーザーが「q」を入力するまで行を出力できるようにするメインイベントループを実装します。 コードを調べて、手順の目的を理解(または推測)してみてください。ただし、少し後で詳細に検討するため、何かがわからなくても絶望しないでください。 @記号は、ARMアセンブリ言語でコメントを強調するために使用されることに注意してください。

グローバル_start_start:ldr r1、= string1 mov r2、#string1len bl print_string loop:mov r7、#3 @ read mov r0、#0 @ stdin ldr r1、= char mov r2、#2 @ 2文字swi0 ldr r1、 = char ldrb r2、cmp r2、#113 @文字「q」のASCIIコードbeqdone ldr r1、= string2 mov r2、#string2len bl print_string b loop done:mov r7、#1 swi 0 print_string:mov r7、#4 mov r0、#1 swi 0 bx lr .data string1:.ascii "終了するにはqを入力してください!\ n" string1len =。 --string1 string2:.ascii "That wasn" t q ... \ n "string2len = .-- string2 char:.word 0

このプログラムは、文字列の先頭とその長さへのポインタを、書き込みシステムコールの後続の実装のために適切なレジスタに配置することから始まり、その直後に、コードの下にあるprint_stringサブルーチンに移動します。 この遷移を行うために、bl命令が使用されます。その名前は「branchand link」を表し、それ自体が現在のアドレスをコードに格納します。これにより、後でbx命令を使用してアドレスに戻ることができます。 print_stringルーチンは、他のレジスタを埋めるだけで、最初のプログラムと同じ方法で書き込みシステムコールを実行してから、OSカーネルスペースにジャンプし、bx命令で保存されたコードアドレスに戻ります。

呼び出し元のコードに戻ると、loopというラベルが見つかります。ラベルの名前は、しばらくすると戻ってくることをすでに示唆しています。 ただし、最初に、read(番号3)という名前の別のシステムコールを使用して、ユーザーがキーボードを使用して入力した文字を読み取ります。 したがって、ファイルからデータではなくユーザー入力を読み取る必要があるため、値3をレジスタr7に、値0(stdinへのハンドル)をレジスタr0に配置します。

次に、OSカーネルによって読み取られて配置された文字を格納するアドレスをレジスタr1に配置します。この場合、これはデータセクションの最後に記述されているcharメモリ領域です。 (実際、Enterキーコードも格納するため、マシンワード、つまり2文字を格納するためのメモリ領域が必要です。アセンブリ言語を使用する場合は、次の可能性を常に覚えておくことが重要です。役立つ高レベルのメカニズムがないため、メモリ領域がオーバーフローします!)。

メインコードに戻ると、値2がr2レジスタに配置されていることがわかります。これは、格納する2文字に対応し、その後、OSカーネルスペースに移動して読み取り操作を実行します。 ユーザーが文字を入力し、Enterキーを押します。 次に、この文字が何であるかを確認する必要があります。メモリ領域のアドレス(データセクションのchar)をレジスタr1に入れ、ldrb命令を使用して、この値が指すメモリ領域からバイトをロードします。登録。

この場合の角括弧は、データがレジスタ自体ではなく、対象のメモリ領域に格納されていることを示します。 したがって、レジスタr2には、データセクションのメモリ領域charの1文字が含まれています。これは、ユーザーが入力した文字とまったく同じです。 次のタスクは、r2レジスタの内容を113番目のASCII文字である「q」文字と比較することです(www.asciichart.comにある文字表を参照)。 ここで、cmp命令を使用して比較操作を実行し、次に「branch ifequal」を表すbeq命令を使用して、レジスタr2の値が113の場合にdoneラベルにジャンプします。そうでない場合、次に2行目を出力し、b命令を使用してループの先頭にジャンプします。

最後に、doneラベルの後で、最初のプログラムで行ったように、プログラムの実行を終了することをOSカーネルに通知します。 このプログラムを実行するには、最初のプログラムの指示に従ってプログラムをアセンブルしてリンクする必要があります。

そのため、かなり簡潔な形式でかなりの量の情報を検討しましたが、上記のコードを試して、資料を独自に調査する方がよいでしょう。 他の人のコードを変更して、達成された効果を観察することを実験するよりも、プログラミング言語に精通するためのより良い方法はありません。 ループ、比較、およびサブルーチンを使用してユーザー入力および出力データを読み取る単純なARMアセンブリプログラムを開発できるようになりました。 今日までアセンブリ言語を経験したことがない場合は、この記事がこの言語をもう少し理解しやすくし、少数の才能のある開発者だけが利用できる神秘的な工芸品であるという一般的な固定観念を払拭するのに役立つことを願っています。

もちろん、ARMアーキテクチャでのアセンブリ言語の使用に関してこの記事で提供されている情報は、氷山の一角にすぎません。 このプログラミング言語の使用は、常に膨大な数のニュアンスに関連しています。次の記事のいずれかにそれらについて書きたい場合は、お知らせください。 それまでの間、http://tinyurl.com/にあるARMアーキテクチャの中央処理装置を搭載したコンピューターで実行されるLinuxシステム用のプログラムを作成する手法を学ぶための多くの資料を含む優れたリソースにアクセスすることをお勧めします。 nsgzq89。 ハッピープログラミング!

「SchoolofAssembler」シリーズの以前の記事:

初めに かなり珍しいアセンブラー( x86, MCS51また AVR)。 しかし、彼はかなり単純で論理的な組織を持っているので、それはすぐに習得されます。

ロシア語のアセンブリ言語のドキュメントはほとんどありません。 2つのリンクにアクセスすることをお勧めします(もっと見つけて教えていただけますか?ありがたいです):
ARMRISCプロセッサのアーキテクチャと命令セット-http://www.gaw.ru/html.cgi/txt/doc/micros/arm/index.htm
「GBAASM」シリーズの記事からのARMアセンブラの理解、Mike H、trans。 Aquila-http://wasm.ru/series.php?sid = 21。

最後のリンクは私を大いに助け、霧を払いのけました=)。 よく役立つ2番目のことは、奇妙なことに、Cコンパイラです。 ARM用IAREmbedded Workbench(以下、単に IAR EW ARM)。 事実、彼は古くから(ちなみに、すべての自尊心のあるコンパイラのように)Cコードをアセンブラコードにコンパイルすることができました。アセンブラコードは、IARアセンブラによってオブジェクトコードに簡単にコンパイルできます。 したがって、Cで最も単純な関数を記述し、それをアセンブラーにコンパイルするのに最適な方法はありません。どのアセンブラーコマンドが何を実行するか、引数がどのように渡されるか、結果がどのように返されるかがすぐに明らかになります。 1つの石で2羽の鳥を殺します。アセンブラーを学び、同時にアセンブリコードをCのプロジェクトに統合する方法に関する情報を取得します。CRC16カウント機能のトレーニングを行った結果、アセンブラーでフルバージョンを入手しました。

これが元のC関数です(u16はunsigned shortを表し、u32はunsigned intを表し、u8はunsigned charを表します):
//ファイルcrc16.c
u16 CRC16(void * databuf、u32サイズ)
{
u16 tmpWord、crc16、idx;
u8 bitCnt;
#define CRC_POLY 0x1021;

Crc16 = 0;
idx = 0;
while(size!= 0)
{
/ * crc16の上位バイトと入力バイトxorを追加します* /
tmpWord =(crc16 >> 8)^(*(((u8 *)databuf)+ idx));
/ *結果を上位バイトcrc16に書き込みます* /
tmpWord<<= 8;
crc16 = tmpWord +(0x00FF&crc16);
for(bitCnt = 8; bitCnt!= 0; bitCnt--)
{
/ *バッテリー残量が多いCRCを確認します* /
if(crc16&0x8000)
{
crc16<<= 1;
crc16 ^ = CRC_POLY;
}
そうしないと
crc16<<= 1;
}
idx ++;
サイズ - ;
}
crc16を返します。
}

IAR EWARMアセンブリコードを生成するのは非常に簡単です。 (プロジェクトに追加された)crc16.cファイルのオプションにdawを入れました 継承された設定を上書きする、次に[リスト]タブに3つのチェックボックスを入れます- 出力アセンブラファイル, ソースを含めるコールフレーム情報を含める(最後のチェックボックスはおそらく省略できますが、不要なものがたくさん生成されます CFI-指令)。 コンパイル後、ファイルはproject_folder \ ewp \ at91sam7x256_sram \ List \ crc16.sになります。 このファイルは、Cファイルと同じくらい簡単にプロジェクトに追加できます(正常にコンパイルされます)。

もちろん、カットされていないバージョンのCコードをCコンパイラーに挿入すると、アセンブラーのリストが表示されたので、何も理解できませんでした。 しかし、関数から1つを除くすべてのC演算子を削除すると、より明確になりました。 次に、段階的にC演算子を追加しました。これが、最終的に起こったことです。

; ファイルcrc16.s
名前crc16
パブリックCRC16

CRC_POLY EQU 0x1021

セクション `.text`:コード:NOROOT(2)

// u16 CRC16(void * databuf、u32サイズ)
; R0-結果を返す、CRC16
; R1-サイズパラメータ
; R2-databufパラメーター(R0を入力したとき)
; R3、R12-一時レジスタ

CRC16:
PUSH(R3、R12);入力すると、R3とR13を保存する必要があることがわかりました。
; 必要はありません。 しかし、私は皆のためにそれを保つことにしました
; ハプニング。
MOVS R2、R0;現在はR2 == databuf
MOV R3、#+ 0
MOVS R0、R3; crc16 = 0
CRC16_LOOP:
CMP R1、#+ 0;すべてのバイトが処理されました(サイズ== 0)?
BEQ CRC16_RETURN;はいの場合、終了します
LSR R3、R0、#+ 8; R3 = crc16 >> 8
LDRB R12 、; R12 = * databuf
EOR R3、R3、R12; R3 = * databuf ^ HIGH(crc16)
LSL R3、R3、#+ 8; R3<<= 8 (tmpWord <<= 8)
AND R0、R0、#+ 255; crc16&= 0x00FF
ADD R0、R0、R3; crc16 = tmpWord +(0x00FF&crc16)
MOV R12、#+ 8; bitCnt = 8
CRC16_BIT_LOOP:
BEQ CRC16_NEXT_BYTE; bitCnt == 0?
TST R0、#0x8000;すべてのビットがまだ処理されているわけではありません。
BEQ CRC16_BIT15ZERO; crc16の最上位ビットを確認します。
LSL R0、R0、#+ 1; crc16<<= 1
MOV R3、#+(LOW(CRC_POLY)); crc16 ^ = CRC_POLY
ORR R3、R3、#+(HIGH(CRC_POLY)<< 8) ;
EOR R0、R3、R0;
B CRC16_NEXT_BIT

CRC16_BIT15ZERO:
LSL R0、R0、#+ 1; crc16<<= 1
CRC16_NEXT_BIT:
SUBS R12、R12、#+ 1; bitCnt--
B CRC16_BIT_LOOP;

CRC16_NEXT_BYTE:
ADD R2、R2、#+ 1; databuf ++
SUBS R1、R1、#+ 1;サイズ-
BCRC16_LOOP;すべてのバイトをループします

CRC16_RETURN:
POP(R3、R12);レジスタの復元
BX LR;サブルーチンの終了、R0 == crc16

IARCコンパイラは驚くほど優れたコードを実行します。 私はそれをほとんど最適化することができませんでした。 彼はコンパイラーが使用したい追加の一時レジスターのみを破棄し(R3とR12で十分でしたが、何らかの理由でLRを追加の一時レジスターとして使用しました)、カウンターをチェックしてフラグを設定する不要なコマンドもいくつか削除しました(必要なチームにSサフィックスを追加するだけです)。

このセクションでは、ARM7TDMIプロセッサの命令セットについて説明します。

4.1フォーマットの簡単な説明

このセクションでは、ARMおよびThumb命令セットについて簡単に説明します。

命令セットテーブルの要点を表1.1に示します。

ARM7TDMIプロセッサは、ARMv4Tアーキテクチャに基づいています。 両方の命令セットの詳細については、ARMアーキテクチャリファレンスマニュアルを参照してください。

表1.1。 テーブルの鍵

ARM命令セットのフォーマットを図1.5に示します。

ARM命令セット形式の詳細については、「ARMアーキテクチャリファレンスマニュアル」を参照してください。

図1.5。 ARM命令セットフォーマット

一部の命令コードは未定義ですが、未定義の命令を検索することはありません。たとえば、ビット6が1に変更された乗算命令などです。このような命令は使用できません。 それらの効果は将来変更される可能性があります。 ARM7TDMIプロセッサの一部としてこれらの命令コードを実行した結果は予測できません。

4.2ARM命令の簡単な説明

ARM命令セットを表1.2に示します。

表1.2。 ARM命令の簡単な紹介

オペレーション アセンブラ構文
発送 発送 MOV(cond)(S)Rd、
発送不可 MVN(cond)(S)Rd、
登録するSPSR転送 MRS(cond)Rd、SPSR
CPSRを転送して登録する MRS(cond)Rd、CPSR
SPSRレジスタ転送 MSR(条件)SPSR(フィールド)、Rm
CPSRの転送 MSR(条件)CPSR(フィールド)、Rm
SPSRフラグへの定数の受け渡し MSR(cond)SPSR_f、#32bit_Imm
CPSRフラグへの定数の受け渡し MSR(cond)CPSR_f、#32bit_Imm
算術 添加 ADD(cond)(S)Rd、Rn、
キャリーフォールディング ADC(条件)(S)Rd、Rn、
減算 SUB(cond)(S)Rd、Rn、
キャリーによる減算 SBC(cond)(S)Rd、Rn、
減算逆減算 RSB(cond)(S)Rd、Rn、
キャリーで逆減算 RSC(cond)(S)Rd、Rn、
乗算 MUL(cond)(S)Rd、Rm、Rs
乗算-累積 MLA(条件)(S)Rd、Rm、Rs、Rn
長い符号なし数値の乗算 UMULL
乗算-長い値の符号なしの累積 UMLAL(cond)(S)RdLo、RdHi、Rm、Rs
署名された長いの乗算 SMULL(cond)(S)RdLo、RdHi、Rm、Rs
乗算-長い値の符号付き累積 SMLAL(cond)(S)RdLo、RdHi、Rm、Rs
比較 CMP(cond)Rd、
比較ネガティブ CMN(cond)Rd、
頭の体操 検査 TST(cond)Rn、
等価性チェック TEQ(cond)Rn、
ログ。 と AND(cond)(S)Rd、Rn、
除く また EOR(cond)(S)Rd、Rn、
ORR ORR(cond)(S)Rd、Rn、
少しクリア BIC(cond)(S)Rd、Rn、 >
遷移 遷移 (条件)ラベル
リンクをたどる (条件)ラベル
一連の指示のナビゲートと変更 (条件)Rn
読む 言葉 LDR(cond)Rd、
LDR(cond)T Rd、
バイト LDR(cond)B Rd、
LDR(cond)BT Rd、
符号付きバイト LDR(cond)SB Rd、
ハーフワード LDR(cond)H Rd、
記号付きのハーフワード LDR(cond)SH Rd、
複数のデータブロックを使用した操作 -
  • 予備的な増分で
  • LDM(条件)IB Rd(, !} {^}
  • その後の増分で
  • LDM(条件)IA Rd(, !} {^}
  • 事前デクリメント付き
  • LDM(条件)DB Rd(, !} {^}
  • その後のデクリメント
  • LDM(条件)DA Rd(, !} {^}
  • スタック操作
  • LDM(条件) Rd(, !}
  • スタック操作とCPSRリカバリ
  • LDM(条件) Rd(, !} ^
    ユーザーレジスタを使用したスタック操作 LDM(条件) Rd(, !} ^
    録音 言葉 STR(cond)Rd、
    ユーザーモード設定の単語 STR(cond)T Rd、
    バイト STR(cond)B Rd、
    ユーザーモード設定のバイト STR(cond)BT Rd、
    ハーフワード STR(cond)H Rd、
    複数のデータブロックに対する操作 -
  • 予備的な増分で
  • STM(条件)IB Rd(, !} {^}
  • その後の増分で
  • STM(条件)IA Rd(, !} {^}
  • 事前デクリメント付き
  • STM(条件)DB Rd(, !} {^}
    o続いてデクリメント STM(条件)DA Rd(, !} {^}
  • スタック操作
  • STM(条件) Rd(, !}
  • ユーザーレジスタを使用したスタック操作
  • STM(条件) Rd(, !} ^
    交換 言葉 SWP(cond)Rd、Rm、
    バイト SWP(条件)B Rd、Rm、
    コプロセッサー データ操作 CDP(条件)p , 、CRd、CRn、CRm、
    コプロセッサからARMレジスタに転送 MRC(条件)p , 、Rd、CRn、CRm、
    ARMレジスタからコプロセッサに転送 MCR(条件)p , 、Rd、CRn、CRm、
    読む LDC(条件)p 、CRd、
    録音 STC(条件)p 、CRd、
    ソフトウェア割り込み SWI 24bit_Imm

    ARMモードのコマンドシステムについて詳しく知ることができます。

    アドレッシングモード

    アドレッシングモードは、さまざまな命令で使用される手順であり、命令で使用される値を生成します。 ARM7TDMIプロセッサは、次の5つのアドレッシングモードをサポートしています。

    • モード1-データ処理命令のシフトオペランド。
    • モード2-ワードまたは符号なしバイトの読み取りと書き込み。
    • モード3-ハーフワードまたはロードサインバイトの読み取りと書き込み。
    • モード4-複数の読み取りと書き込み。
    • モード5-コプロセッサーの読み取りと書き込み。

    アドレッシングモードとそのタイプおよびニーモニックコードを表1.3に示します。

    表1.3。 アドレッシングモード

    アドレッシングモード アドレス指定のタイプまたはモード ニーモニックコードまたはスタックタイプ
    モード2 オフセット定数
    オフセットレジスタ
    スケーリングレジスタオフセット
    事前にインデックス付けされたオフセット -
    絶え間ない !
    登録 !
    スケールレジスタ !
    !
    !
    !
    !
    -
    絶え間ない 、#+ / -12bit_Offset
    登録 、+ /-Rm
    スケールレジスタ
    モード2、特権 オフセット定数
    オフセットレジスタ
    スケーリングレジスタオフセット
    オフセットとそれに続くインデックス付け -
    絶え間ない 、#+ / -12bit_Offset
    登録 、+ /-Rm
    スケールレジスタ 、+ /-Rm、LSL#5bit_shift_imm
    、+ /-Rm、LSR#5bit_shift_imm
    、+ /-Rm、ASR#5bit_shift_imm
    、+ /-Rm、ROR#5bit_shift_imm
    モード3、 > オフセット定数
    !
    後続のインデックス作成 、#+ / -8bit_Offset
    登録
    事前インデックス作成 !
    後続のインデックス作成 、+ /-Rm
    モード4、読書 IA、その後の増分 FD、完全下降
    ED、空の降順
    DA、その後のデクリメント FA、完全昇順
    DBの事前デクリメント EA、空の昇順
    モード4、録音 IA、その後の増分 FD、完全下降
    IB、プリインクリメント ED、空の降順
    DA、その後のデクリメント FA、完全昇順
    DBの事前デクリメント EA、空の昇順
    モード5、コプロセッサーデータの転送 オフセット定数
    事前インデックス作成 !
    後続のインデックス作成 、#+ /-(8bit_Offset * 4)

    オペランド2

    オペランドは、データまたは周辺機器を参照する命令の一部です。 オペランド2を表1.4に示します。

    表1.4。 オペランド2

    フィールドを表1.5に示します。

    表1.5。 田畑

    条件フィールド

    条件フィールドを表1.6に示します。

    表1.6。 条件フィールド

    フィールドタイプ サフィックス 説明 状態
    状態(条件) EQ 等しい Z = 1
    NE 等しくない Z = 0
    Cs 符号なし以上 C = 1
    CC 署名なし C = 0
    MI ネガティブ N = 1
    PL 正またはゼロ N = 0
    VS オーバーフロー V = 1
    VC オーバーフローなし V = 0
    やあ 署名されていない C = 1、Z = 0
    LS 符号なし以下 C = 0、Z = 1
    GE 多かれ少なかれ N = V(N = V = 1またはN = V = 0)
    LT 小さい NV(N = 1およびV = 0)または(N = 0およびV = 1)
    GT もっと Z = 0、N = V(N = V = 1またはN = V = 0)
    LE 以下 Z = 0またはNV(N = 1およびV = 0)または(N = 0およびV = 1)
    AL 常に真実 フラグは無視されます

    4.3Thumb命令セットの簡単な説明

    Thumb命令セットのフォーマットを図1.6に示します。 ARM命令セット形式の詳細については、 『ARM Architectural Reference Manual』を参照してください。


    図1.6。 サム命令セットフォーマット

    Thumb命令セットを表1.7に示します。

    表1.7。 Thumb命令セットの簡単な説明

    手術 アセンブラ構文
    転送(コピー) 定数 MOV Rd、#8bit_Imm
    シニアからジュニア MOV Rd、Hs
    ジュニアからシニア MOV Hd、Rs
    シニアからシニアへ MOV Hd、Hs
    算術 添加 Rd、Rs、#3bit_Immを追加
    ジュニアをジュニアに追加 Rd、Rs、Rnを追加
    古いものを若いものに追加する Rd、Hsを追加
    ジュニアをシニアに追加 Hd、Rsを追加
    シニアをシニアに追加 Hd、Hsを追加
    一定の加算 ADD Rd、#8bit_Imm
    SPに付加価値を与える ADD SP、#7bit_Imm ADD SP、#-7bit_Imm
    キャリーオーバー加算 ADC Rd、Rs
    減算 SUB Rd、Rs、Rn SUB Rd、Rs、#3bit_Imm
    定数の減算 SUB Rd、#8bit_Imm
    キャリー減算 SBC Rd、Rs
    符号の反転 NEG Rd、Rs
    乗算 MUL Rd、Rs
    ジュニアとジュニアを比較する CMP Rd、Rs
    ジュニアとシニアを比較する CMP Rd、Hs
    シニアとジュニアを比較する CMP Hd、Rs
    シニアとシニアを比較する CMP Hd、Hs
    ネガティブに比較 CMN Rd、Rs
    定数と比較する CMP Rd、#8bit_Imm
    頭の体操 AND Rd、Rs
    除く また EOR Rd、Rs
    また ORR Rd、Rs
    少しクリア BIC Rd、Rs
    発送不可 MVN Rd、Rs
    ビットテスト TST Rd、Rs
    シフト/回転 論理的な左シフト LSL Rd、Rs、#5bit_shift_imm LSL Rd、Rs
    論理右シフト LSR Rd、Rs、#5bit_shift_imm LSR Rd、Rs
    算術右シフト ASR Rd、Rs、#5bit_shift_imm ASR Rd、Rs
    右回転 ROR Rd、Rs
    遷移 条件付きジャンプ -
    BEQラベル
    BNEラベル
    BCSラベル
    BCCラベル
    BMIラベル
    BPLラベル
    BVSラベル
    BVCラベル
  • C = 1、Z = 0
  • BHIラベル
  • C = 0、Z = 1
  • BLSラベル
  • N = 1、V = 1またはN = 0、V = 0
  • BGEラベル
  • N = 1、V = 0またはN = 0、V = 1
  • BLTラベル
  • Z = 0および((NまたはV = 1)または(NまたはV = 0))
  • BGTラベル
  • Z = 1または((N = 1またはV = 0)または(N = 0およびV = 1))
  • BLEラベル
    無条件ジャンプ Bラベル
    リンクを長押しします BLラベル
    オプションの状態変更 -
  • ml単位のアドレスで。 登録
  • BX Rs
  • stのアドレスで。 登録
  • BX Hs
    読む オフセット定数付き -
  • 言葉
  • LDR Rd、
  • ハーフワード
  • LDRH Rd、
  • バイト
  • LDRB Rd、
    オフセットレジスタ付き -
  • 言葉
  • LDR Rd、
  • ハーフワード
  • LDRH Rd、
  • ハーフワード
  • LDRSH Rd、
    LDRB Rd、
  • 符号付きバイト
  • LDRSB Rd、
    PCプログラムカウンターに関連して LDR Rd、
    スタックポインタSPを基準に LDR Rd、
    住所 -
  • PCを使用する
  • ADD Rd、PC、#10bit_Offset
  • SP付き
  • ADD Rd、SP、#10bit_Offset
    複数の読書 LDMIA Rb !、
    録音 オフセット定数付き -
  • 言葉
  • STR Rd、
  • ハーフワード
  • STRH Rd、
  • バイト
  • STRB Rd、
    オフセットレジスタ付き -
  • 言葉
  • STR Rd、
  • ハーフワード
  • STRH Rd、
  • バイト
  • STRB Rd、
    SPと比較して STR Rd、
    複数のエントリ STMIA Rb !、
    スタックを置く/ポップオフ レジスタをスタックにプッシュします 押す
    LRとレジスタをスタックにプッシュします 押す
    スタックからレジスタをポップします ポップ
    レジスタとPCをスタックからポップします ポップ
    ソフトウェア割り込み - SWI 8bit_Imm

    現在、非常に単純なマイクロコントローラーでさえプログラミングするために、CまたはC ++言語のサブセットである高水準言語が原則として使用されています。

    ただし、プロセッサのアーキテクチャとその機能を調査する場合は、アセンブリ言語を使用することをお勧めします。これは、そのようなアプローチのみが、調査中のアーキテクチャの機能を確実に識別できるためです。 このため、アセンブリ言語を使用してさらにプレゼンテーションを実行します。

    ARM7コマンドの検討に進む前に、ARM7の次の機能に注意する必要があります。

      32ビット命令のARMと16ビット命令のTHUMBの2つの命令セットをサポートします。 以下は32ビットの命令セットです。ARMという単語はこのフォーマットに属する命令を意味し、ARM7という単語は実際のCPUです。

      2つの32ビットアドレス形式のサポート:ビッグエンディアンプロセッサとリトルエンディアンプロセッサ)。 最初のケースでは、最上位ビット(最上位ビット-MSB)はワードの最下位ビットにあり、2番目のケースでは-最上位ビットにあります。 これにより、高級言語を使用する場合に、32ビットプロセッサの他のファミリとの互換性が保証されます。 ただし、ARMコアを備えた多くのプロセッサフ​​ァミリでは、ビッグバイトオーダーのみが使用され(つまり、MSBがアドレスの最上位ビットです)、プロセッサでの作業が大幅に簡素化されます。 ARM7に使用されるコンパイラは両方の形式のコードで動作するため、単語の形式が正しいことを確認する必要があります。そうしないと、結果のコードが「裏返し」になります。

      ALUで使用する前に、「パス上で」オペランドの1つのさまざまなタイプのシフトを実行する機能

      任意のコマンドの条件付き実行のサポート

      演算結果のフラグの変更を禁止する可能性。

        1. 条件付きコマンドの実行

    ARM命令セットの重要な機能の1つは、任意の命令の条件付き実行をサポートすることです。 従来のマイクロコントローラでは、条件付き命令は条件付きジャンプ命令のみであり、個々のビットの状態をチェックまたは変更するためのコマンドなど、他の多くの命令もあります。 ARM命令セットでは、命令コードの最上位4ビットが常にCPSRレジスタの条件フラグと比較されます。 それらの値が一致しない場合、復号化段階のコマンドはNOPコマンドに置き換えられます(操作なし)。

    これにより、遷移が「短い」プログラムセクションの実行時間が大幅に短縮されます。 したがって、たとえば、実数の係数を持つ2次方程式と負の判別式を持つ任意の根を解く場合、平方根を計算する前に判別式の符号を変更し、その結果を回答の虚数部に割り当てる必要があります。

    この問題の従来の解決策では、条件付きジャンプコマンドを入力する必要があります。 このコマンドの実行には、少なくとも2クロックサイクルが必要です。新しいアドレス値の復号化とコマンドカウンターへのロード、およびコマンドパイプラインをロードするための追加のクロックサイクル数です。 正の判別式で条件付きコマンド実行を使用する場合、符号変更コマンドは空の操作に置き換えられます。 これはコマンドパイプラインをクリアせず、損失は1クロックサイクル以下になります。 条件付き命令をNOP命令に置き換えることが、従来の条件付き分岐命令の実行よりも効率的であり、関連するパイプラインの補充がその深さに等しいことが判明するしきい値。 三。

    この機能を実装するには、条件フラグのテスト済み状態を定義する16個のプレフィックスのいずれかをアセンブラーコマンドの基本的なニーモニック表記に追加する必要があります(Cは同じです)。 これらのプレフィックスを表に示します。 3.したがって、各コマンドには16のオプションがあります。 たとえば、次のコマンド:

    MOVEQ R1、#0x008

    これは、最後のデータ処理コマンドの結果が「等しい」か、結果が0で、CPSRレジスタのフラグ(Z)がそれに応じて設定された場合にのみ、数値0x00800000がR1レジスタにロードされることを意味します。

    表3

    コマンドプレフィックス

    意味

    Zがインストールされています

    Zが落ちた

    インストール済み

    以上(符号なし)

    Cがリセットされます

    以下(署名なし)

    Nがインストールされています

    否定的な結果

    Nクリア

    正または0

    Vセット

    オーバーフロー

    Vが落ちた

    オーバーフローなし

    インストールすると、

    Zが落ちた

    上(署名なし)

    Sが落ちた、

    Zがインストールされています

    以下(署名なし)

    (符号付き)以上

    NはVと等しくありません

    少ない(象徴的)

    Zが落ちたAND

    (NはVに等しい)

    もっと(象徴的)

    Zが設定されているOR

    (NはVと等しくありません)

    以下(署名済み)

    (無視)

    無条件の実行

    トピックの続き:
    コンピュータ

    ソフトウェアを更新するにはどうすればよいですか? ソフトウェアを更新するさまざまな方法を提供します。つまり、メモリカードを使用して更新するか、「...