アセンブラ初心者スレッド 2©2ch.net
■ このスレッドは過去ログ倉庫に格納されています
オブジェクト指向のアセンブラアプリのお薦めを教えてください。 初心者なので意味が解らないんだけど、オブジェクト指向って考え方なんじゃないの? ところで、2つの32BIT レジスタの値を、64BITレジスタの上位32bit、下位32bit に 分けて入れる場合、shld を使えばいいのかな? 32BIT 時代の場合は、ebx <--- dx:ax としたい場合、 mov bx,dx shl ebx,16 mov bx,ax などとしたもんだけど、64BIT モードで、rbx <--- edx:eax としたい場合、 例えば、 shld rdx,rax,32 mov rbx,rbx とするのかな。 あ、訂正させて。 多分、正しくは: shl rax,32 shld rdx,rax,32 mov rbx,rbx もしかして、mmx レジスタや xmm レジスタの、shuffle 命令なども使えたりする のだろうか?? すまん。最訂正。 shl rax,32 shld rdx,rax,32 mov rbx,rdx shl rdx, 16 mov ebx, eax or rbx, rdx なるほど、つまり: shl rdx, 32 mov ebx, eax or rbx, rdx と。OR を使うとは全く思いつかなかった。 考えてみれば、C言語ではいつも、シフトとOR使ってやってた・・・。 【まとめ】 64BIT モードでは、mov 命令などの destination が、32BITレジスタの場合、 原則的に、対応する64BITレジスタの上位32BITがゼロクリアされてしまうの で注意が必要。蛇足だが、destinationが16BITレジスタや8BITレジスタの 場合は、対応する64BITレジスタの残りの上位ビットは完全に保持される。 この結果、2つの同じビット数のレジスタを2倍のビット数のレジスタの上位、下位 に代入したい場合、 32BIT Legacy Mode で、ebx <--- dx:ax としたい場合、 mov bx,dx shl ebx,16 mov bx,ax ;ebx(64BITモードだと、rbx) の上位16ビット(上位48BIT)は、 ;直前のまま変化しない。 で良かったが、 64BIT モードで、rbx <--- edx:eax としたい場合、 shl rdx, 32 ; 左シフトの結果、rdx の下位32BIT は、0になる。 mov ebx, eax ; rbx の上位32BIT は(勝手に)0クリアされる。 or rbx, rdx や、 shl rax,32 ; 左シフトの結果、rax の上位32BITに元の eaxが入る事になる。 shld rdx,rax,32 ; rdx : rax をひとまとめにして 32BIT分、左シフト。 mov rbx,rdx などとする必要がある。 64bitレジスタの上位が0になるのは無駄な依存関係を無くすため 32bitレジスタの部分書き換えは大きなペナルティが発生するので注意 アセンブラで高速なコードを書くなら インテルの最適化マニュアルを一通り読むことを勧める IACAも非常に便利 アセンブラは普通に書いただけでもCに比べてバイナリも非常に小さいし高速に動いてくれるよね ペナルティを避けるため、普通は同じレジスタでも別のでもいいから、movzx使って上位ビットが0だって明示してから使うべき 今回の64ビットバージョンのrbx <--- edx:eax としたい場合は関係なかったけど、 mov bx,dxをやった後にebxやrbxを参照するとペナルティが発生するのは32ビットでも一緒だけどね >>79 確か、64BIT モードだと、そもそも、movzx reg64,reg32 に相当する専用命令が 存在せず、その代わりに、単なる mov reg32,reg32 を使う想定になっているはず。 なぜなら、後者でも上位32BITが0になるから。 一方、movsx reg64,reg32 については、新しい新命令として、movsxd なる ものが導入されている。なぜなら、movsx の第二オペランドは、32BIT時代 から、8BIT か、16BIT レジスタしか採りえないから。命令自体で 第二オペランドのビット数が固定されていて、第二オペランドが 8BIT と 16BIT で別々の opcode になっていて、0x66 prefix でデータサイズ を変えた場合は、第一オペランドのサイズが変わるだけ、という仕様だった から。つまり、64BITモードでも、第二オペランドのBIT数は、 opcode 自体で固定されてしまっているので、REX.W や 0x66 prefix では 第二オペランドのBIT数の変更はされない、というのが素直な解釈だったから。 [test3.asm] .data my_mojiretu db 'somothing', 0 ;1 .code mov rax,offset my_mojiretu ;2 xor rbx,rbx mov al,my_mojiretu[rbx] ;3 ------------------------------------------------------------------- 2 は、大丈夫なのに、3 だと、以下のようなエラーになる。なぜ? test3.obj に「絶対アドレス」の relocation 情報が入ることにはなるハズなんだけど。 J:\Develop\C\masm64_s\masm64>d:\ml64\bin\ml64.exe /Fl test3.asm /link /libpath:" C:\Program Files (x86)\Windows Kits\8.0\Lib\win8\um\x64" /subsystem:windows /def aultlib:kernel32.lib /defaultlib:user32.lib /entry:main Microsoft (R) Macro Assembler (x64) Version 9.00.30729.207 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: test3.asm Microsoft (R) Incremental Linker Version 9.00.30729.207 Copyright (C) Microsoft Corporation. All rights reserved. /OUT:test3.exe test3.obj "/libpath:C:\Program Files (x86)\Windows Kits\8.0\Lib\win8\um\x64" /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main test3.obj : error LNK2017: 'ADDR32' relocation to 'my_title' invalid without /LA RGEADDRESSAWARE:NO LINK : fatal error LNK1165: link failed because of fixup errors エラーでは、my_title になってるけど、5ch に登校する際に、 ソースを my_mojiretu に変えただけなので、同じと思って。 あと、somothing は、something の typo。 あー。例外的に2の部分の、 mov reg,imm は、imm に 64BIT 即値が入れられるけど、3の部分では、 ModRMの間接参照の [ebx + disp] を使ってるけど、この形では、dispに32BIT までしか入れられないからだな。。。 なるほど。つまり、 mov al,my_mojioretu[ebx] は、 mov al,[ebx + disp64] としたくても、AMD64 ではそのようなメモリオペランドが使えないので、 mov al,[ebx + disp32] にしかアセンブルできないと。 だから、絶対アドレスが、32BIT を超えるような値になった場合にはどうしようも ないと。 ちょっと勉強になった 64bitでasm触ることなくなったもんなー >>85 実際に試してないから推測だけど、2では64bitレジスタに代入しているのに対して 3では8bitレジスタALに代入しようとしている。EAXかRAXに代入してみれば動くんではないかと。 AMD64 では、ModRM を使った MemoryOperand の [reg64 + disp] のような形式の場合、 disp の BIT 数が、最大でも、 [reg64 + disp32] のように 32BIT までが限界で、64BIT の disp64 は存在していない。 imm も「一般的には」、imm32 までで、imm64 は使えない。 ところが、mov reg,imm に関しては、例外的に mov reg64, imm64 という命令が存在している。また、似た話として、 ModRM を使わない MemoryOperand では、disp64 のようなものが例外的に 存在していて、 mov reg,[disp64] というものが存在している。ただし、意味的には、[disp64] ではなく、[moffset64] のような意味合いで、 mov reg,[moffset64] や mov reg,moffset64 と書かれることがある。この場合、[] が付いてなくても意味は同じ(混乱注意)。 ちなみに、mem64 は、データサイズが 64BIT という意味なので、 qword ptr [xxxx] の意味になるので、また違う。 つまり、レジスタに入れてアクセスできるアドレスは64BITなのに、固定アドレスを入れてアクセス できるメモリは、32BIT に制限されやすい、ということ。ただし、上記のような例外が用意されている ので、絶対に32BITを超えた固定アドレスのメモリをアクセスできないというわけではない。 ちなみに、 .data ラベル名 db 'xxxx',0 ;1 .code lea ebx,ラベル名 ;2 のような場合、一見、2の第二オペランドは、ModRM に encode されるので、 lea ebx,[disp32] になってしまうから、この場合も、32BIT の限界に遭遇してしまうのではないかと 思ってしまうかもしれない。ところが、実際には、 lea ebx,[rip + rel_addr32] のように命令ポインタの rip 相対のアドレスとして、encode されるので、また、事情が 違ってくる。結論的には、この場合は、ラベル名がリンクに指定した obj の中にある 限りにおいては、32BIT の限界を特に気にすることは無い。 >>92 間違った。 ebx ではなく、rbx だった: lea rbx,ラベル名 ;2 lea rbx,[disp32] lea rbx,[rip + rel_addr32] >>41 実は、以下のようにしておくだけで、SEH例外のサポートも含めて、 ほぼ、push, pop は自由に出来るようになるらしい。 1. 関数の最初と最後を以下のように書く : push rbp mov rbp, rsp ・・・ pop rbp ret 2. rbp を frame pointer に使っているということを、関数の prolog に書く。 【「ほぼ」 の例外】 call 関数名などの関数コールの直前では、rsp を16バイトアラインしてある 必要があること。それさえ気をつけていれば、push, pop は自由に行える。 【16倍とスタックアラインについて】 push rbp のおかげで、return address の 8 バイトと合わせて、直後からは、 上手く勝手に rsp が 16 バイトにアラインされた状態になってくれる。 だから、余り難しい事を考える必要は無い。 frame pointer(rbp) を使わない場合は、関数の通常部分では、rsp を最初から最後まで 固定しなくては、構造化例外を処理出来なくなる。 なので、rsp が変化する事になる push, pop が使えなくなる。 (なお、人間にとっては便利でも、コンパイラにとっては、元々 push, pop は使いにくい 命令であった。) x86、x64 アーキテクチャでは、mov reg, reg/mem のような事は出来ても、 mov mem, mem は出来ない。ところが、この、push, pop についてはそれが 出来てしまう数少ない例外。 例えば、 push qword ptr [ebp + disp1] ;1 や push qword ptr [esp + disp1] ;2 で、ローカルの auto 変数をスタックに保存する事が出来る。ところが、 mov qwrod ptr [esp + disp1], qword ptr [esp + disp2] ;3 とは出来ない。だから、push 命令を使うと、実はパフォーマンスが向上する可能性も 秘めてはいるかも知れない。ただし、これも、レジスタの個数が十分沢山ある 近年では、必ずしもそうとも言えないかもしれない。レジスタの個数が不足して スタックに保存したくなった場合、 mov [esp + disp1], reg ;4 と書けばいいわけであって、3 のように書く必要性は、近年では下がっているようだから。 >>95 誤:esp, ebp 正:rsp, rbp 【なぜ、3の必要性が少ないかについて】 3のようにしなくても、そもそも、3の第二オペランドの値は、メモリ上において あるのだから、レジスタが不足しても値が消えてしまう事はない。だから、 保存の必要も無い。 レジスタが不足した場合に保存する必要があるのは、必ずレジスタの値。 だから、mov [rsp + disp], reg の形式で十分保存できてしまう。 内部的にロード、ストアは別命令 アセンブラの命令数とかあまり関係ない >>92 コードセグメントのリテラルならrip相対でエンコードされると思うけど、データセグメントでそれ可能だったっけ? 最近アセンブリ言語いじってないから確認してないけど、それが可能だったら>>85 の3はエラーにならないのでは。 >>95 最適化マニュアルのスループットとレイテンシの表見れば書いてあるけど、 push、popはロードユニットやストアユニットだけじゃなくALUも使う。その分無駄。 push、popはコードサイズに制限のある、プロローグ、エピローグ部分で使うことはあるけど、 あまり必要性はなくなったよ。 push、popみたいに複数のμOPになる命令は利用可能なデコーダーも制限されるので 他の命令のデコードも阻害しやすい。 演算命令のメモリオペランドみたいに、フュージョンされて簡単デコーダーで発行できるようになったのもあるけど。 >>99 >コードセグメントのリテラルならrip相対でエンコードされると思うけど、 >データセグメントでそれ可能だったっけ? 可能。 >>99 >最近アセンブリ言語いじってないから確認してないけど、それが可能だったら>>85 の3はエラーにならないのでは。 エラーメッセージをよく読むと書いてあるけど、linker に、 /LA RGEADDRESSAWARE:NO というオプションを渡すと、エラーが消える。 これは、[rbp+disp] の disp には、32BIT までしか入れられないので敢えて エラーを出しているだけなので、本当は特に問題にはならない。 >>103 それ、可能と言えば可能だけど、64bit化のメリットのかなりの部分が消えてしまうし、 DLLでも制限なく使えるんだっけ? と思って調べたらやっぱ問題あるよなあ。 https://www.webtech.co.jp/blog/optpix_labs/programing/6387/ >>103 mov cl, hogehoge これでアセンブルして、dumpbin /relocations test01.obj これで見ると hogehogeがREL32になる(PC相対の32bitオフセット) ラベルの種類としてRIP相対のREL32はあるけど 通常のレジスタに32bit相対のラベルの種類がないのでは? だからエラーになると 簡単なWindowsのアプリを作ってWinMainのアドレスを表示すると 0x000000003fdf1770 俺の環境ではこんな値が出た これってアドレスとしては1GBくらいの位置 下位32bitの絶対アドレスで指定すると符号付と解釈した場合に 残り1GBの範囲しかアクセスできない だから、64bitのWindowsや/3GBスイッチを指定した32bitWindowsでは 下位32bitの絶対アドレスで指定するなとマイクロソフトは決めたのでは? RIP相対なら開始アドレスに関係なくRIPの相対値なので プログラムがロードされた位置に関わらず2GBまでアクセスできる 同じようなプログラムを32bitで作ってコンパイルしたらWinMainの開始アドレスは 0x013215a0 アドレスとしては19MBくらいの位置 64bitアプリは32bitアプリよりもずっと高位のアドレスにロードされるんだろうね だから64bitアプリでは下位32bitでの絶対アドレス指定は禁止してるのかも >>108 通常のプログラムが扱えるのは論理的なアドレスだからな 関係なくないよ >>85 の mov al,my_mojiretu[rbx] ;3 のような下位32bitの絶対アドレスで指定する場合、扱えるデータの量が減る 上の例では0から2GBの範囲しかアクセスできないのに 論理アドレスで1GB付近からロードされたら扱えるスタティックに割り当てられたデータの量が極端に減ってしまう だからこそ >>85 の mov al,my_mojiretu[rbx] ;3 のようなコードを書くとリンカがエラーを吐くようになってるんだろうね 下位32bitの絶対アドレスで指定するなと ほとんどのCPUでは64bitのアドレスを直接指定する方法は限定されてて、実行速度も遅くなる だからWindowsやLinuxでは64bitでもデフォルトではスタティックに割り当てられたシンボルは だいたい32bitの値として扱ってる (Linuxではコンパイラのオプションでメモリモデルを指定できて スタティックなシンボルを64bitの値として扱うこともできる) 64bit Windowsの場合、かなり高位のアドレスにロードされるようだから RIP相対の32bitの値として扱ってるのだろう ちなみにこれはスタティックに割り当てられたデータだけで動的に割り当てられたデータは 64bitのポインタ値で扱われるのでユーザプログラムが扱える全仮想メモリ領域に配置できる 64bitARMの場合、Linuxではスタティックなシンボル値読み込む場合 adrp x0, :pg_hi21:hogehoge add x0, x0, :lo12:hogehoge これで読み込む これで33bitのページ単位でPC相対のアドレスを読み込める つまり64bitのARMではスタティックなシンボル値は33bitのPC相対アドレスとして扱ってる (相対なのはページ単位なので下位12bitは動かせない、下位12bitは絶対アドレス値を足してるので) 64bitのARMのadrp命令と同等の命令はRISC-Vや最近発表されたnanoMIPSなどでも採用されてる RISC-VやnanoMIPSは33bitではなく32bitだが スタティックなシンボル値の制約はアセンブラではなくあくまでコンパイラの仕様だが (コンパイラ側でシンボルを扱う場合にあえて下位32bitしか読み込まない仕様にしてる) 最近のアセンブラプログラミングでは高級言語とリンクすることが多いので必須な知識 >>111 > ちなみにこれはスタティックに割り当てられたデータだけで動的に割り当てられたデータは > 64bitのポインタ値で扱われるのでユーザプログラムが扱える全仮想メモリ領域に配置できる > ほとんどのCPUでは64bitのアドレスを直接指定する方法は限定されてて、実行速度も遅くなる つまりこういうこと? ・動的に割り当てられたデータは64bitでアクセスできるけど実行速度が遅くなる ・静的に割り当てられたデータは32bitでアクセスするように制限されてるけど実行速度は速い。 ・ただしLinuxの場合、メモリモデルを指定して再コンパイルすればこの制限はなくなる >>115 >・動的に割り当てられたデータは64bitでアクセスできるけど実行速度が遅くなる そもそも動的なデータは64bitのポインタで管理されるので最初から全アドレスにアクセスできる >・静的に割り当てられたデータは32bitでアクセスするように制限されてるけど実行速度は速い。 違う、あくまでシンボルを32bitのアドレスとして読み込んでるだけでデータサイズは関係ない x86_64でも64bitのアドレスを指定して読み込む命令は限られてるし、 実行速度の面でも効率が悪いのでRIP相対で32bit分だけを使ってる たとえば、 mov rcx, data01 とした場合、data01の部分はRIP相対の32bitアドレスになる、読み込まれるデータは64bitの値 RISC CPUだとアドレスを読み込んでからデータに読み書きするという2段階になるので たとえば、64bitのARMだと adrp x0, :pg_hi21:data01 add x0, x0, :lo12:data01 ldr x0, [x0] と下位33bitでPC相対アドレスとしてシンボルが読み込まれる RISC-Vの絶対アドレスだと lui a0, a0, data01 addi a0, a0, data01 ld a0, (a0) PC相対だと 1: auipc a0, %pcrel_hi(data01) addi a0, a0, %pcrel_lo(1b) ld a0, (a0) こうなる (%pcrel_lo()では対応するpcrel_hi()のラベルを指定) RISC-Vの絶対アドレスのところが間違ってた 不正解 lui a0, a0, data01 addi a0, a0, data01 ld a0, (a0) 正解 lui a0, %hi(data01) addi a0, a0, %lo(data01) ld a0, (a0) >>116 アドレッシングの話をしているだと思ったのではしょったけどこう書けばいいですかね ・動的に割り当てられたデータは64bitアドレッシングでアクセスできるけど実行速度が遅くなる ・静的に割り当てられたデータは32bitアドレッシングでアクセスするように制限されてるけど実行速度は速い ・どちらの場合も読み書きできるデータサイズに制限はない ・ただしLinuxの場合、メモリモデルを指定して再コンパイルすればこの制限はなくなる 何で、動的に割り当てられた変数と静的に割り当てられた変数の アクセス速度を比較するのか意味不明だが 一番アクセスが速いのはローカル変数だと思うぞ ローカル変数はdisplacement付きのスタックポインタ間接アドレッシングでアクセスできるので ほとんどのCPUでdisplacementが届く範囲なら1命令でロード、ストアができるからね >>119 今までの話の流れで言うと、速度を比較するのが目的ではなくて、 なぜ静的に割り当てられたデータは32bitアドレッシングでアクセスするように制限されているのか? ですよ。普通に考えたら64bitアドレッシングが制限なくできて当たり前だろ。なんで?ってことです。 x86の64bitモードでのアドレッシングは全て64bit >>120 命令サイズを節約してるだけ バラバラにコンパイルされた.oに対して後から命令長は変えられないんでデフォでそうなってる 変えたければラージモデルの類のコンパイルオプションがあるはず >>120 64bitのデータだろうが32bitデータだろうがデータがキャッシュに入ってれば キャッシュからデータを読み出す速度は変わらないが、 64bitアドレスを即値で指定すると CISC CPUの場合、64bitアドレスの分、命令のオペランドの長さが長くなるので 今時の複数の命令を同時発行できるCPUだと一度に発行できる命令の数が減るので遅くなる 64bitのアドレスだけで8バイトにもなるからね RISC CPUの場合は64bitのアドレスを一度に読み込めないので複数の命令に分けて読み込むが 64bitのアドレスを読み込むと命令数が増える 結局、静的データやジャンプ先のラベルはは32bitアドレスに限定したほうが結果的に速くなる たいていのプログラムで静的データやジャンプ先のラベルで 32bitの範囲を超えるような応用プログラムは極一部の分野だけだしね その極一部の応用プログラムのために性能を落とす必要はない スパコンでも使われるLinuxではメモリモデルとして 静的データやジャンプ先のラベルを64bitで扱うようにコンパイルするオプションがある このページはPGIのコンパイラのページだがわかりやすく説明してある https://www.softek.co.jp/SPG/Pgi/TIPS/opt_64.html はこのページ ARMやx86_64、POWERのgccでも-mcmodel=largeや-mcmodel=mediumがあったりする (ARMでは-mcmodel=largeはあるが-mcmodel=mediumはない) https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html https://gcc.gnu.org/onlinedocs/gcc/RS_002f6000-and-PowerPC-Options.html >>104 確か、そのリンカオプションは、DLL作成時に指定するとエラーになる。 また、アプリの場合だと、relocation 情報が strip されて、EXE ファイルの中にはなくなっているのに対し、DLLだと relocation 情報が最後まで残されたままになっている。 >>105 あなたが使っている用語が x86 や x64 とは合わずめちゃくちゃなので何を聞いているのか分からないが、 hogehoge db 'xxxx', 0 mov cl, hogehoe 1 とすると、 mov cl,[offset hogehode] と言う意味になり、encode としては、 mov cl, [RIP + (offset hogehode - offset label1)] label1: のように、RIP 相対の disp mov cl, [RIP + disp32] に encode される。この場合の disp32 は、意味としては、rel32 で、 RIP からの32BIT相対アドレス。 一方、似て非なるものとして、 mov cl, hogehode[rbx] とすると、 mov cl, [offset hogehode + rbx] 即ち、 mov cl, [rbx + disp32] と翻訳される。この場合の、disp32 は、32BIT絶対アドレス。 同じ、disp32 でも、意味は異なる。しかし、disp は、32BITまでで、 64BIT 値のものは存在していない。一方、64BITアドレスも一応は使えるように、 mov REG64, ADDR64 ; REG64 は、RAX, RBX, RCX, EDX, RBP, RSI, EDI など。 や mov ACC, [ADDR64] ; ACC は、al, ax, eax, rax という命令が、 専用命令として特別扱いとして存在しているが、特殊中の特殊。 >>124 訂正: hogehoge db 'xxxx', 0 mov cl, hogehoge とすると、 mov cl,[offset hogehoge] と言う意味になり、encode としては、 mov cl, [RIP + (offset hogehoge - offset label1)] label1: のように、RIP 相対の disp mov cl, [RIP + disp32] に encode される。この場合の disp32 は、意味としては、rel32 で、 RIP からの32BIT相対アドレス。 ちなみに、masm では、 hoge1 db 'aaa' ;1 と hoge2: db 'aaa' ;2 は明確に区別されており、hoge1 は、byte 型の配列変数のような取り扱いになり、 mov al, hoge1 ;1 は、masm には通らないが、旧来のアセンブラの感覚で言えば、意味的には、 mov al, qword [offset hoge1] ;3 となり、hoge2 の方は、直後の db 命令とは全く関係の無い単なる near アドレスと解釈され、 mov rax, hoge2 ;4 は、意味的には、 mov rax, offset hoge2 ;5 となる、ただし、4は、もしかするとそれ自体が文法上、masm の構文に合わないかもしれない。 masm は、「マクロアセンブラ」であって、通常のアセンブラとは結構異なるので。 >>125 誤: mov al, qword [offset hoge1] ;3 正: mov al, byte [offset hoge1] ;3 これも、masm 流には、 mov al, byte ptr [offset hoge1] ;3 nasm 流には、 mov.b al, [offset hoge1] ;3 みたいな感覚。みたいなだけで、実際にはそのまま書くと、エラーになる かも知れない。 >>124 さらに補足すると、 mov cl, [rbx + disp32] の disp32 は、rbx の中身によって意味が変わってきて、 1. disp = 32BIT 相対アドレス (rbx が絶対アドレスの場合) 2. disp = 32BIT 絶対アドレス (rbx が相対アドレスの場合) となる。2. の例としては、 rbx = 配列添え字 * (配列の要素のバイト数) のような場合。1. の例としては、 rbx = 構造体の先頭アドレス のような場合。 >>127 今やってみたが 64bitアプリを通常にコンパイルした場合のWinMainのアドレス 0x000000013f291770 /largeaddressaware:noを付けてコンパイルした場合のWinMainのアドレス 0x0000000001241770 /largeaddressaware:noを付けるとプログラムがロードされるアドレスが変わる 64bitのアプリを通常にコンパイルするとWinMainのアドレスが0x000000013f291770 およそ先頭から5GBの位置 つまり、32bit絶対アドレスでは届かない位置にプログラムがロードされてる /largeaddressaware:noをつけると先頭から18MBくらいの位置 エラーが出るのは32bit絶対アドレスでは届かない位置にロードされる可能性があるからだろうな 補足: /largeaddressaware:noを付けると64bitアプリでもメモリは2GBまでしか使えなくなる つまり、>>128 が示してることは Windowsの64bitアプリでは mov al,my_mojiretu[rbx] のような書き方はしてはいけないということ >>124 XvleiWNbとは別人だよね? /LARGEADDRESSAWARE:NOの辺りからなんか違和感あって、「そうなん?」って感じだったんだけど。 ARMやコンパイラの仕様だとかまで出てきて訳わからなくなるところだったよ。 >一方、似て非なるものとして、 > mov cl, hogehode[rbx] >とすると、 > mov cl, [offset hogehode + rbx] >即ち、 > mov cl, [rbx + disp32] >と翻訳される。この場合の、disp32 は、32BIT絶対アドレス。 同じ、disp32 でも、意味は異なる。 >Windowsの64bitアプリでは >mov al,my_mojiretu[rbx] >のような書き方はしてはいけないということ 今までなんで絶対アドレスが出てくるのか疑問だったけど、コンパイラはこういうコードは吐かない、ってことだよね。 >>131 >>124 XvleiWNbとは別人だよね? 別人。ARMは使ったこと無い。 >>130 でも現実は、複雑。 なぜなら、COFFの仕様的には、obj ではない Image(EXEやDLLの事) のためだけに ある .reloc section には、64BIT 絶対アドレスの再配置も行えるようになっているから。 現状の MS 製の link.exe がどうなっているかはともかく。 >>133 すまん。間違った。 mov al,my_mojiretu[rbx] は、意味的には、 mov al, [rbx + offset my_mojiretu] となって、最後は、 mov al, [rbx + disp32] となるが、disp32 の部分は、disp64 の命令は存在していないので、 .reloc section が 64BIT 絶対アドレスに対応していても、無理だった。 勘違いした。 >>128 一応、論理的には、WinMain は、code (.text) section に置かれる。 my_mojiretu みたいなものは、.data section (など)に置かれる。 my_mojiretu みたいなものは、初期化(初期値)データなので、 2GB も使えれば十分ではあるはず。もし、2GB を超えるのなら、exe ファイルの サイズも 2GB を超えるはず。 という事で、初期化データのおかれた section のアドレスが、2GB 未満 に置かれるならなんとかなるはず。 というか、通常、exe ファイルは、relocation 情報が strip されているので、 確か、Optional Header の BaseOfCode に配置したいアドレスを入れておける。 だから、その値を小さめにしておけば、初期化データのアドレスを 2GB 未満 の位置に配置する事はそんなに難しくないはず。 確か、32BIT 時代は、0x40000 位の値だった。 >>135 正しくは、BaseOfCode ではなく、ImageBase の方だった。 正しい値は、0x40000 ではなく、1桁大きい、0x400000 だった。 64BIT COFF では、変わってるかもしれない。確か、PREFERED_BASE などという名前も記憶にある。 ImageBase Preferred address of first byte of image when loaded into memory; must be a multiple of 64K. The default for DLLs is 0x10000000. The default for Windows CE EXEs is 0x00010000. The default for Windows NT, Windows 95, and Windows 98 is 0x00400000. >>128 自分の勘だと、そのアドレスは、WinMain よりも、DLL の DllMain が置かれるような 値になってるね。 不思議だ。WinMain をそんな大きなアドレスに置く必要性は余り無いハズなので。 初期化データが 2GB 未満に置かれていても、malloc() や、new で確保されるデータは、 64BIT アドレスにできるはずだし。なお、 1_3f29_1770 ↑は、32BIT を超えて、33BIT の値だな・・・。なんちゅう大きな値だろう。 64bitもアドレス空間があるんだから 多少大きくても何の問題も無いだろ >>134 32bitのフラットメモリモデルだと、静的変数はイメージにはオフセットを入れといて、 実行時にローダで書き換えてたんだと思うんだけど、64bitでは絶対アドレスは制約が大きくなるから変だと思ったんだ。 絶対アドレスはコンパイラでも使えなくはないけど、デバイスドライバでメモリマップトI/O操作するような時しか使わないと思う。 64bitでイメージベースが大きくなったのは、セキュリティ関係でランダマイズしたりとか、 mov al,my_mojiretu[rbx] みたいなコードが例外吐いて特定困難なバグが発生するのを防止してるのでは? >>135 コマンドライン用の簡単なC言語の64bitのプログラムで試したが main関数のアドレス=0x000000013f7f1000 data sectionで定義した変数data01のアドレス=0x000000013f7fc087 だったぞ 完全に32bit絶対アドレスの範囲を超えてる 64bitのWindowsアプリを作って.data sectionの変数のアドレスも表示してみた WinMain address = 0x000000013fd419a0 data sectionの変数のアドレス data01 address = 0x000000013fd4d000 /largeaddressaware:noを付けた場合 WinMain address = 0x00000000013619a0 data sectionの変数のアドレス data01 address = 0x000000000136d000 >>139 >64bitでイメージベースが大きくなったのは、セキュリティ関係でランダマイズしたりとか、 >mov al,my_mojiretu[rbx] >みたいなコードが例外吐いて特定困難なバグが発生するのを防止してるのでは? 意味不明だ。別に、 mov al,my_mojiretu[rbx] というコードが悪いわけではない。 むしろ、最適化のためには使った方が効率が良くなる。 >>141-142 もしそうだとすると、VC++ の吐くコードがx64の命令を上手く使いきれてないという事になる。 本来であれば、アドレスの配置を上手く行うだけで、64BIT モードでも特に問題なく mov al,my_mojiretu[rbx] という命令は使えて、かつ、64BIT アドレスの制限を受けるわけでもないのだから。 ちなみに、アプリの EXE は、リンク後は固定アドレスで、ローダーがアドレスを再配置 する事はない。だから、ImageBase を小さい値になるようにリンクしさえすれば、 問題が生じない。 >>139 >64bitでイメージベースが大きくなったのは、セキュリティ関係でランダマイズしたりとか、 通常、コンピュータソフト、特にOSのセキュリティーというのは、そういう人間的なもの ではなくて、もっと厳密な物だ。 ランダマイズして撹乱して相手の目をくらます、などという方法は通常取られない。 実際、EXE ファイルを解析した経験からしても、ランダマイズなどは全く行われていない。 何回リンクしても、同じアセンブリソースや、同じC++ソースなら、全く同じアドレスになる。 異なるソースであっても、ベースアドレスなどは、ほとんどの場合、固定値。 >>138 実際には問題がある。なぜなら、そんなにアドレスが大きいと、さっきから話題の mov al, my_mojiretu[rbx] という命令が使えなくなるからだ。 これは、グローバルな配列変数を、添え字でアクセスするような場合に良いコードに なる事がある。僅かではあるが。もし、この命令が使えないとなると、 mov rdx,offset my_mojiretu mov al,[rdx + rbx] のように、2つの命令を使わなくてはならなくなり、最適化上、不利になる。 そんな事言い出したら 4GBや64KBに限定した方が小さいコードになるから compactモデルにしよう とかいう話にもなる 昔に戻りたい? 何のための64bit? >>147 だから、そういうことじゃなく、EXEファイルの中に、2GBを越える初期化データを 誰が入れたいかって話なんだ。 別に、ImageBase を小さい値にしていても、malloc() や、new するデータは、 6GBでも理論上は確保できるわけで、制限されるのは、EXEファイルの中の 初期化データのサイズが2GBまで、ってだけなんだ。 それで、使えるマシン語の間接オペランドの種類が1つ増やせる。 mov 命令だけでなく、add, sub, mul, div, idiv, lea, addps, addss などにも 全て影響する貴重な間接オペランド。 >>147 8086時代の 64KB では制限が大きすぎて、ほとんどのプログラムで、制限が足かせ になっていたので、32BIT になって、アドレスが 4GB に拡張されて非常に便利になった。 ところが、64BIT 時代になっても、そのときのようなメリットが無いと思うんだ。 AMD vs Intel の競争の結果出てきた産物かも知れない。そういうの、時々ある。 >>145 WindowsではASLRでランダム化普通にやってるらしいですよ http://07c00.hatenablog.com/entry/2013/08/07/033443 OSがプロセスをロードするときに、ランダムな場所にモジュールを配置するセキュリティ機能です。 実際はモジュールだけじゃなく、スタックやヒープなどもランダマイズされたりします。 >>150 少なくとも、EXE の .text, .data セクションは再配置される事は無いはず。 なぜなら、.reloc section が存在せず、再配置する事が原理的に出来なくなっているから。 DLL は、.reloc section が残されているので再配置できる。ただし、再配置しなくても、 全てのシステムのDLLは、最初からアドレスが重ならないような ImageBase になっている ので、再配置されずにそのままおかれるのが、昔は基本であった。 >>148 64bitコードで絶対番地に依存したコードとか 考え方が古いねえ ていうか、 イヤなら2GB限定のコードにすればいい コードもデータも2GBの範囲に割り当てられるから >>152 じゃあなんで、MS純正VSは、いまだに 32BIT コードで動いてるのさ。 x64 は、レジスタが増えた事と、memmove() などが多分、倍の速度で動く事が 最大のメリットじゃないの? アドレス幅が64BITになって現実的なメリットはどこにあるのかな? なにが「じゃあ」だか メリットが無いと思うなら2GB限定のアプリにすれば良い って書いたのが見えなかった? >>154 だから、メリットはあるよ。 レジスタが増えること、AVX, AVX2, AVX512 で、YMM, ZMM レジスタでベクトル の次元が大きくなったSIMD 命令が使えること、3オペランドの以上のSIMD命令が使える 事、メモリ転送の速度が倍近くになること。malloc(), new が、2GB を超えて行える事。 初期化サイズが2GBを越えるメリットは余り無いと思ってるけど。 >>149 アドレス空間は広いほうがいいに決まっている ユーザーアドレス空間の断片化は基本防止できない、だったら入れものが広いほういい >>148 結局、 mov al,my_mojiretu[rbx] こんなコードを書くと64bitではLINKする時にエラーが出る /largeaddressaware:noを指定するとLINKでエラーは出なくなるが、 /largeaddressaware:noを指定すると動的メモリも含め2GB以下のメモリしか扱えなくなる ■ このスレッドは過去ログ倉庫に格納されています
read.cgi ver 07.5.1 2024/04/28 Walang Kapalit ★ | Donguri System Team 5ちゃんねる