2017-02-20 40 views
3

При изучении исходного линукса кода, я вижу много asssembly кода, который выглядит следующим образом:Как этот код сборки получает разницу между физическим смещением и смещением страницы?

adr r0, 1f 
ldmia r0, {r3-r7} 
mvn ip, #0 
subs r3, r0, r3 @ PHYS_OFFSET - PAGE_OFFSET 
... 
    .align 
1: .long . 
    .long __pv_table_begin 
    .long __pv_table_end 
2: .long __pv_phys_pfn_offset 
    .long __pv_offset 

первым, он начинается с адром и в первой строке выше, я понял, что adr r0, 1f означает, что он сохранит адрес, где 1: начинается с r0.

ldmia r0, {r3-r7} означает, что она будет загружать значения, начиная с адреса, сохраненного в r0 (что указывает на 1:) в регистрах R3, R4, R5, R6, R7. Поэтому

r3=.

r4=__pv_table_begin

r5=__pv_table_end

r6=__pv_phys_pfn_offset

r7=__pv_offset.

Теперь, часть часть, которую я не получаю: subs r3,r0,r3 Я не совсем уверен, что r3=. средства, но я предположил, что, в конечном счете r3 содержать значение своего собственного адреса.

Смутно говоря, r0 и r3 значения оба значения адреса, указывающие на ту же позицию 1:. Но я думаю, что их значения разные, потому что один, если физический адрес, а другой - виртуальный адрес. (< - это чисто мое предположение).

Вот почему я подумал, как-то код пытается получить разницу между этими двумя через subs r3, r0, r3.

Я не уверен, правильно ли я догадываюсь, и даже не знаю, какой именно физический адрес и виртуальный адрес. Кроме того, в комментарии упоминается, что вычитание приведет к разнице между физическим смещением и смещением страницы. Я читал о страницах, связанных с виртуализацией памяти, но я не могу связать это знание с этим вычитанием смещения.

+1

Я не специалист по ARM, но 'adr' вычисляет адрес' 1' во время выполнения (т. Е. Всякий раз, когда этот код был перемещен), в то время как директива gas '.' - это смещение w.r.t. текущий раздел (или аналогичный, я тоже не специалист GAS). –

ответ

1

Почему вы не попробовали?

hello: 
    .long . 
    .long 0x11111111 
    .long 0x22222222 
    .long 0x33333333 
    .long 0x44444444 
    .long 0x55555555 
    .long 0x66666666 

.globl TEST 
TEST: 
    adr r0,hello 
    bx lr 

связаны между собой и разобраны

0000803c <hello>: 
    803c: 0000803c andeq r8, r0, r12, lsr r0 
    8040: 11111111 tstne r1, r1, lsl r1 
    8044: 22222222 eorcs r2, r2, #536870914 ; 0x20000002 
    8048: 33333333 teqcc r3, #-872415232 ; 0xcc000000 
    804c: 44444444 strbmi r4, [r4], #-1092 ; 0xfffffbbc 
    8050: 55555555 ldrbpl r5, [r5, #-1365] ; 0xfffffaab 
    8054: 66666666 strbtvs r6, [r6], -r6, ror #12 

00008058 <TEST>: 
    8058: e24f0024 sub r0, pc, #36 ; 0x24 
    805c: e12fff1e bx lr 

TEST возвращает 0x803C, как мы оба ожидали.

Первый элемент в списке, хотя может быть вашей тайной. Обратите внимание, как они используют ярлык точки, чтобы указать здесь или этот адрес, поэтому первым элементом в списке является адрес начала списка. которые r0 уже могли бы сделать только mov r3, r0, но, возможно, сжигают эту инструкцию, просто загружая ее и сжигая баран с одной инструкцией. кто знает ...

так

.globl TEST 
TEST: 
    adr r0,hello 
    ldmia r0,{r3} 

    mov r3,r0 
    bx lr 

который возвращает то же значение 0x803C.

Теперь

.globl TEST 
TEST: 
    adr r0,hello 
    ldmia r0,{r3} 
    subs r3,r0,r3 

    mov r0,r3 
    bx lr 

как и ожидалось, что возвращает ноль, так что смысл всего этого? Обратите внимание, что весь этот раздел не зависит от позиции? Ну, что, если я изменить линкер думать, что это загружается где-то еще ...

MEMORY 
{ 
    ram : ORIGIN = 0xA000, LENGTH = 0x1000000 
} 

продуцирующие

0000a03c <hello>: 
    a03c: 0000a03c andeq r10, r0, r12, lsr r0 
    a040: 11111111 tstne r1, r1, lsl r1 
    a044: 22222222 eorcs r2, r2, #536870914 ; 0x20000002 
    a048: 33333333 teqcc r3, #-872415232 ; 0xcc000000 
    a04c: 44444444 strbmi r4, [r4], #-1092 ; 0xfffffbbc 
    a050: 55555555 ldrbpl r5, [r5, #-1365] ; 0xfffffaab 
    a054: 66666666 strbtvs r6, [r6], -r6, ror #12 

0000a058 <TEST>: 
    a058: e24f0024 sub r0, pc, #36 ; 0x24 
    a05c: e8900008 ldm r0, {r3} 
    a060: e0503003 subs r3, r0, r3 
    a064: e1a00003 mov r0, r3 
    a068: e12fff1e bx lr 

, но все-таки выполнить в том же месте дает 0xFFFFE000 что -0x2000 из-за направления Я изменил свой компоновщик, если я изменил его на 0x5000 вместо 0xA000. В качестве разницы получается 0x3000.

Так что этот код делает это

.long . 

это время компиляции АСПД выполнение и используют во время выполнения ПК так что этот код будет обнаруживая разницу между фактическим адресом памяти, где таблицей является и во время компиляции адрес, где находится таблица. если элементы в таблице времени компиляции адрес

hello: 
    .long . 
    .long one 
    .long two 
    .long three 
one: 
    .long 0x44444444 
two: 
    .long 0x55555555 
three: 
    .long 0x66666666 


0000503c <hello>: 
    503c: 0000503c andeq r5, r0, r12, lsr r0 
    5040: 0000504c andeq r5, r0, r12, asr #32 
    5044: 00005050 andeq r5, r0, r0, asr r0 
    5048: 00005054 andeq r5, r0, r4, asr r0 
0000504c <one>: 
    504c: 44444444 strbmi r4, [r4], #-1092 ; 0xfffffbbc 
00005050 <two>: 
    5050: 55555555 ldrbpl r5, [r5, #-1365] ; 0xfffffaab 
00005054 <three>: 
    5054: 66666666 strbtvs r6, [r6], -r6, ror #12 

Тогда для того, чтобы использовать эту таблицу переходов или посмотреть таблицу, вам нужно знать скомпилированный адрес против адреса во время выполнения, так что вы можете настроить время компиляции адрес в код.

Использование терминов, таких как физическое и страница, я думаю, что страница неверна, но это может быть виртуальное время vs link (я думаю, что скомпилированный - это неправильный термин, время связывания и время выполнения), все равно время выполнения и время ссылки зависит от причины поскольку разница - независимость позиции или виртуализация. Если вы работаете в операционной системе, время соединения и время выполнения должны быть одинаковыми, физическое не может быть обнаружено таким образом, так как процессор (adr), по крайней мере, как задокументировано, видит значение на ПК, а ПК не знает физического с виртуального, то есть от край сердечника в mmu. Поэтому я думаю, что оба термина физическое и страница неправильно используются здесь, но это только мое мнение.

Если вы удаляете -fPIC из своих параметров компилятора и не делаете его независимым от положения кодом, мне интересно, не будет ли это беспокоиться обо всем этом и просто используйте таблицу как есть.

+0

ahh, вы сказали, что исходный код linux, это не скомпилированный код. они делают его независимым от положения. Если я правильно помню, вы можете загрузить ядро ​​и, по крайней мере, первую часть, которая сжимает реальное ядро, а драйверы не зависят от положения. Возможно, все это (что поражает производительность). –

+0

Возможно, автор используется для x86, где термин, подобный странице, имеет смысл. это можно было бы вырезать и вставить, а затем перевести на руку, не меняя комментариев. –

+0

.long vs .word - это еще одна подсказка, слово в x86 - 16 бит в руке 32 бита, длина которых варьируется в x86 (16-битные дни C-версия длиной 32 бита, в 32-битные дни это было 32 бита, но с 64-битным он изменился до 64, по крайней мере, с одним/некоторыми компиляторами). Хотя это все ассемблер gnu, что само по себе не означает ничего из-за характера привычек ассемблера gnu против компиляторов или реальности. В этом случае слово и .long являются взаимозаменяемыми, мне показалось странным, что .long был выбран здесь, когда программист по рукам, вероятно, долго не думал бы. –

1

Есть некоторые концепции, которые вам нужно понять в коде.

  1. Код получает адрес ссылки, в котором разрешена абсолютная адресация.
  2. Адрес (ПК) может отличаться.

Код, который вы ищете, - это код переселения, который исправит абсолютную адресацию «link» с адресом ПК.

adr r0, 1f 
ldmia r0, {r3-r7} 
mvn ip, #0 
subs r3, r0, r3 @ PHYS_OFFSET - PAGE_OFFSET 
... 
    .align 
1: .long . 
    .long __pv_table_begin 
    .long __pv_table_end 
2: .long __pv_phys_pfn_offset 
    .long __pv_offset 

adr r0, 1f и 1: .long . кажутся одинаковыми. Однако есть тонкая разница. Строка 1: .long . сохранит адрес ссылки как локальную метку «1:». adr r0, 1f преобразует в add r0, pc, #offset, и поэтому адрес времени выполнения будет размещен в R0. ldmia r0, {r3-r7} загружает множество значений, но значение R3 является адресом ссылки локальной метки. Наконец, subs r3, r0, r3 поместит разницу между адресом прогона и адресом ссылки в R3; корректирующий термин.

Таблица представляет собой список адресов ссылок, которые должны быть исправлены. Это позволяет использовать код «не-ПОС» для разных адресов.

Комментарий прямо над кажется, чтобы быть полезным,

/* __fixup_pv_table - patch the stub instructions with the delta between 
* PHYS_OFFSET and PAGE_OFFSET, which is assumed to be 16MiB aligned and 
* can be expressed by an immediate shifter operand. The stub instruction 
* has a form of '(add|sub) rd, rn, #imm'. 
*/ 

Это зависит от значения Kconfig ARM_PATCH_PHYS_VIRT, который, как представляется, needed for the Keystone2 CPU/SOC согласно early_paging_init с машины починки окна для физических адресов.


Причина необходима эта таблица только для кода ядра, который нужен физический адрес памяти, как правило, общаться с DMA устройствами, но и достаточно широко в мм (или управление виртуальной памятью) кода, который является производительность критически к ОС подкачки. В Linux встроенная функция просто вычитает/добавляет смещение разницы между адресами физических/физических ядер; virt_to_phys, phys_to_virt и т. Д. Когда эта функция используется в драйвере/модуле, ее необходимо устранить, когда скомпилированное физическое/виртуальное различие не совпадает с тем, что происходит при запуске изображения. Существует всегда смежный карта памяти ядра (виртуальное переназначение является фиксированным смещением только для ядра адреса)

Некоторых машин находятся в memory.h и может быть поучительными в понимании того, что происходит. Обратите внимание на комментарий,

/* 
* Physical vs virtual RAM address space conversion. These are 
* private definitions which should NOT be used outside memory.h 
* files. Use virt_to_phys/phys_to_virt/__pa/__va instead. 
* 
* PFNs are used to describe any physical page; this means 
* PFN 0 == physical address 0. 
*/ 

См:

+0

И физические, и виртуальные должны быть исправлены; создавать виртуальные виртуальные и создавать физические (PHYS_OFFSET) для запуска физических. Linux традиционно выполняется с развязкой 3G/1G, поэтому виртуальный запуск равен 0xc0000000; однако людям нужно было это изменить по разным причинам. Также физическая память отличается для разных SOC/производителей.Для запуска одного двоичного файла на нескольких типах поставщиков SOC, «PHYS_OFFSET» является динамическим (ну, это самомодифицирующийся код в pv_table jazz выше, который исправляется). Как бы то ни было, виртуальное физическое различие ядра работает постоянно для всех адресов. –