web-dev-qa-db-ja.com

アプリケーションによってリンカースクリプトで定義されたアクセスシンボル

リンカースクリプトファイルで、2つのシンボルを定義しました

define symbol _region_RAM_start__     = 0xC0000000;
define symbol _region_RAM_end__       = 0xC00fffff; 

そして、私はそれらを以下に示すようにエクスポートしました

export symbol _region_RAM_start__;
export symbol _region_RAM_end__;

アプリケーションコードから、これらのシンボルにアクセスしようとします

extern const unsigned int _region_RAM_start__;
extern const unsigned int _region_RAM_end__;
....
int GetRAMSize()
{
    int size = 0;
    unsigned int address_1 = _region_RAM_start__;
    unsigned int address_2 = _region_RAM_end__;
    size = address_2 - address_1 + 1U;
    return size;
}

ここで、戻り値は0x00100000になると予想しましたが、取得するのは0だけです。したがって、デバッガーに目を向けると、_region_RAM_start__と_region_RAM_end__の値はそれぞれ0xC0000000と0xC00fffffですが、address_1とaddress_2の値は0です。 。

コンパイラの最適化は「なし」に設定されています。これはしばらくの間私を悩ませてきました。私がここで見逃している非常に明白な何かがありますか(私が最初にこれを行うべきではないことを除いて)

解決策 n.mに感謝します。答えのために

  unsigned int address_1 = (unsigned int) (&_region_RAM_start__);

それ以外の場合、address_1とaddress_2の両方にガベージ値が含まれます(つまり、アドレス0xC0000000と0xC00fffffでそれぞれ使用可能な値ですが、このコードの観点からはガベージです)

21
ka05

それは少し古いですが、とにかく答えます…

ld manual から:

ソースコードからリンカースクリプトで定義された変数にアクセスするのは直感的ではありません。特に、リンカスクリプトシンボルは、高級言語での変数宣言と同等ではなく、値を持たないシンボルです。

先に進む前に、コンパイラは、シンボルテーブルに格納されるときに、ソースコード内の名前を別の名前に変換することがよくあることに注意することが重要です。たとえば、Fortranコンパイラーは通常、アンダースコアを追加または追加し、C++は広範な名前マングリングを実行します。したがって、ソースコードで使用されている変数の名前と、リンカースクリプトで定義されているのと同じ変数の名前との間に不一致がある可能性があります。たとえば、Cでは、リンカースクリプト変数は次のように呼ばれる場合があります。

extern int foo;

ただし、リンカースクリプトでは、次のように定義される場合があります。

_foo = 1000;

ただし、残りの例では、名前の変換が行われていないことを前提としています。

シンボルがCなどの高級言語で宣言されると、2つのことが起こります。 1つ目は、コンパイラがプログラムのメモリに、シンボルのを保持するのに十分なスペースを予約することです。 2つ目は、コンパイラがプログラムのシンボルテーブルに、シンボルのアドレスを保持するエントリを作成することです。つまり、シンボルテーブルには、シンボルの値を保持するメモリブロックのアドレスが含まれています。したがって、たとえば、ファイルスコープでの次のC宣言:

int foo = 1000;

シンボルテーブルに「foo」というエントリを作成します。このエントリは、数値1000が最初に格納されているintサイズのメモリブロックのアドレスを保持します。

プログラムがシンボルを参照すると、コンパイラは最初にシンボルテーブルにアクセスしてシンボルのメモリブロックのアドレスを見つけ、次にそのメモリブロックから値を読み取るコードを生成します。そう:

foo = 1;

シンボルテーブルでシンボルfooを検索し、このシンボルに関連付けられているアドレスを取得してから、値1をそのアドレスに書き込みます。一方:

int * a = & foo;

シンボルテーブルでシンボルfooを検索し、アドレスを取得してから、このアドレスを変数「a」に関連付けられたメモリブロックにコピーします。

対照的に、リンカスクリプトのシンボル宣言は、シンボルテーブルにエントリを作成しますが、それらにメモリを割り当てません。したがって、これらは値のないアドレスです。したがって、たとえば、リンカースクリプトの定義は次のとおりです。

foo = 1000;

メモリ位置1000のアドレスを保持する@samp {foo}というシンボルテーブルにエントリを作成しますが、アドレス1000には特別なものは何も格納されません。これは、valueリンカースクリプトで定義されたシンボルの-値がありません-実行できるのは、定義されたリンカースクリプトのアドレスを使用することだけですシンボル。

したがって、ソースコードでリンカースクリプトで定義されたシンボルを使用している場合は、常にシンボルのアドレスを取得し、その値を使用しようとしないでください。たとえば、.ROMと呼ばれるメモリのセクションの内容を.FLASHと呼ばれるセクションにコピーし、リンカスクリプトに次の宣言が含まれているとします。

start_of_ROM   = .ROM;
end_of_ROM     = .ROM + sizeof (.ROM);
start_of_FLASH = .FLASH;

その場合、コピーを実行するためのCソースコードは次のようになります。

extern char start_of_ROM, end_of_ROM, start_of_FLASH;

memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);

「&」演算子の使用に注意してください。それらは正しいです。

19
Keith Ape

これらはアクセスしようとしている汎用アドレスシンボルであり、必ずしも特定の型へのポインタではないため、unsigned intとして宣言するのではなく、次のように宣言します。

extern void _region_RAM_START;

その場合、&_ region_RAM_STARTは適切なタイプ 'void *'になります。

4
John Meacham

以下のコードは期待どおりに機能するはずです。

extern const volatile unsigned int _region_RAM_start__;

extern const volatile unsigned int _region_RAM_end__;

....
int GetRAMSize()

{

int size = 0;

unsigned int address_1 = &_region_RAM_start__;

unsigned int address_2 = &_region_RAM_end__;

size = address_2 - address_1 + 1U;

return size;

}
1