요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
================================
Application Data Integrity (ADI)
================================
SPARC M7 processor adds the Application Data Integrity (ADI) feature.
ADI allows a task to set version tags on any subset of its address
space. Once ADI is enabled and version tags are set for ranges of
address space of a task, the processor will compare the tag in pointers
to memory in these ranges to the version set by the application
previously. Access to memory is granted only if the tag in given pointer
matches the tag set by the application. In case of mismatch, processor
raises an exception.
Following steps must be taken by a task to enable ADI fully:
1. Set the user mode PSTATE.mcde bit. This acts as master switch for
the task's entire address space to enable/disable ADI for the task.
2. Set TTE.mcd bit on any TLB entries that correspond to the range of
addresses ADI is being enabled on. MMU checks the version tag only
on the pages that have TTE.mcd bit set.
3. Set the version tag for virtual addresses using stxa instruction
and one of the MCD specific ASIs. Each stxa instruction sets the
given tag for one ADI block size number of bytes. This step must
be repeated for entire page to set tags for entire page.
ADI block size for the platform is provided by the hypervisor to kernel
in machine description tables. Hypervisor also provides the number of
top bits in the virtual address that specify the version tag. Once
version tag has been set for a memory location, the tag is stored in the
physical memory and the same tag must be present in the ADI version tag
bits of the virtual address being presented to the MMU. For example on
SPARC M7 processor, MMU uses bits 63-60 for version tags and ADI block
size is same as cacheline size which is 64 bytes. A task that sets ADI
version to, say 10, on a range of memory, must access that memory using
virtual addresses that contain 0xa in bits 63-60.
ADI is enabled on a set of pages using mprotect() with PROT_ADI flag.
When ADI is enabled on a set of pages by a task for the first time,
kernel sets the PSTATE.mcde bit for the task. Version tags for memory
addresses are set with an stxa instruction on the addresses using
ASI_MCD_PRIMARY or ASI_MCD_ST_BLKINIT_PRIMARY. ADI block size is
provided by the hypervisor to the kernel. Kernel returns the value of
ADI block size to userspace using auxiliary vector along with other ADI
info. Following auxiliary vectors are provided by the kernel:
============ ===========================================
AT_ADI_BLKSZ ADI block size. This is the granularity and
alignment, in bytes, of ADI versioning.
AT_ADI_NBITS Number of ADI version bits in the VA
============ ===========================================
IMPORTANT NOTES
===============
- Version tag values of 0x0 and 0xf are reserved. These values match any
tag in virtual address and never generate a mismatch exception.
- Version tags are set on virtual addresses from userspace even though
tags are stored in physical memory. Tags are set on a physical page
after it has been allocated to a task and a pte has been created for
it.
- When a task frees a memory page it had set version tags on, the page
goes back to free page pool. When this page is re-allocated to a task,
kernel clears the page using block initialization ASI which clears the
version tags as well for the page. If a page allocated to a task is
freed and allocated back to the same task, old version tags set by the
task on that page will no longer be present.
- ADI tag mismatches are not detected for non-faulting loads.
- Kernel does not set any tags for user pages and it is entirely a
task's responsibility to set any version tags. Kernel does ensure the
version tags are preserved if a page is swapped out to the disk and
swapped back in. It also preserves that version tags if a page is
migrated.
- ADI works for any size pages. A userspace task need not be aware of
page size when using ADI. It can simply select a virtual address
range, enable ADI on the range using mprotect() and set version tags
for the entire range. mprotect() ensures range is aligned to page size
and is a multiple of page size.
- ADI tags can only be set on writable memory. For example, ADI tags can
not be set on read-only mappings.
ADI related traps
=================
With ADI enabled, following new traps may occur:
Disrupting memory corruption
----------------------------
When a store accesses a memory location that has TTE.mcd=1,
the task is running with ADI enabled (PSTATE.mcde=1), and the ADI
tag in the address used (bits 63:60) does not match the tag set on
the corresponding cacheline, a memory corruption trap occurs. By
default, it is a disrupting trap and is sent to the hypervisor
first. Hypervisor creates a sun4v error report and sends a
resumable error (TT=0x7e) trap to the kernel. The kernel sends
a SIGSEGV to the task that resulted in this trap with the following
info::
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIDERR;
siginfo.si_addr = addr; /* PC where first mismatch occurred */
siginfo.si_trapno = 0;
Precise memory corruption
-------------------------
When a store accesses a memory location that has TTE.mcd=1,
the task is running with ADI enabled (PSTATE.mcde=1), and the ADI
tag in the address used (bits 63:60) does not match the tag set on
the corresponding cacheline, a memory corruption trap occurs. If
MCD precise exception is enabled (MCDPERR=1), a precise
exception is sent to the kernel with TT=0x1a. The kernel sends
a SIGSEGV to the task that resulted in this trap with the following
info::
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIPERR;
siginfo.si_addr = addr; /* address that caused trap */
siginfo.si_trapno = 0;
NOTE:
ADI tag mismatch on a load always results in precise trap.
MCD disabled
------------
When a task has not enabled ADI and attempts to set ADI version
on a memory address, processor sends an MCD disabled trap. This
trap is handled by hypervisor first and the hypervisor vectors this
trap through to the kernel as Data Access Exception trap with
fault type set to 0xa (invalid ASI). When this occurs, the kernel
sends the task SIGSEGV signal with following info::
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ACCADI;
siginfo.si_addr = addr; /* address that caused trap */
siginfo.si_trapno = 0;
Sample program to use ADI
-------------------------
Following sample program is meant to illustrate how to use the ADI
functionality::
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <asm/asi.h>
#ifndef AT_ADI_BLKSZ
#define AT_ADI_BLKSZ 48
#endif
#ifndef AT_ADI_NBITS
#define AT_ADI_NBITS 49
#endif
#ifndef PROT_ADI
#define PROT_ADI 0x10
#endif
#define BUFFER_SIZE 32*1024*1024UL
main(int argc, char* argv[], char* envp[])
{
unsigned long i, mcde, adi_blksz, adi_nbits;
char *shmaddr, *tmp_addr, *end, *veraddr, *clraddr;
int shmid, version;
Elf64_auxv_t *auxv;
adi_blksz = 0;
while(*envp++ != NULL);
for (auxv = (Elf64_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) {
switch (auxv->a_type) {
case AT_ADI_BLKSZ:
adi_blksz = auxv->a_un.a_val;
break;
case AT_ADI_NBITS:
adi_nbits = auxv->a_un.a_val;
break;
}
}
if (adi_blksz == 0) {
fprintf(stderr, "Oops! ADI is not supported\n");
exit(1);
}
printf("ADI capabilities:\n");
printf("\tBlock size = %ld\n", adi_blksz);
printf("\tNumber of bits = %ld\n", adi_nbits);
if ((shmid = shmget(2, BUFFER_SIZE,
IPC_CREAT | SHM_R | SHM_W)) < 0) {
perror("shmget failed");
exit(1);
}
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shm attach failed");
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE|PROT_ADI)) {
perror("mprotect failed");
goto err_out;
}
/* Set the ADI version tag on the shm segment
*/
version = 10;
tmp_addr = shmaddr;
end = shmaddr + BUFFER_SIZE;
while (tmp_addr < end) {
asm volatile(
"stxa %1, [%0]0x90\n\t"
:
: "r" (tmp_addr), "r" (version));
tmp_addr += adi_blksz;
}
asm volatile("membar #Sync\n\t");
/* Create a versioned address from the normal address by placing
* version tag in the upper adi_nbits bits
*/
tmp_addr = (void *) ((unsigned long)shmaddr << adi_nbits);
tmp_addr = (void *) ((unsigned long)tmp_addr >> adi_nbits);
veraddr = (void *) (((unsigned long)version << (64-adi_nbits))
| (unsigned long)tmp_addr);
printf("Starting the writes:\n");
for (i = 0; i < BUFFER_SIZE; i++) {
veraddr[i] = (char)(i);
if (!(i % (1024 * 1024)))
printf(".");
}
printf("\n");
printf("Verifying data...");
fflush(stdout);
for (i = 0; i < BUFFER_SIZE; i++)
if (veraddr[i] != (char)i)
printf("\nIndex %lu mismatched\n", i);
printf("Done.\n");
/* Disable ADI and clean up
*/
if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE)) {
perror("mprotect failed");
goto err_out;
}
if (shmdt((const void *)shmaddr) != 0)
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(0);
err_out:
if (shmdt((const void *)shmaddr) != 0)
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
Application Data Integrity 개요
1-13SPARC M7 processor는 Application Data Integrity(ADI) 기능을 추가합니다. ADI를 사용하면 task가 자신의 address space 중 원하는 subset에 version tag를 설정할 수 있습니다.
ADI를 활성화하고 task의 address range에 version tag를 설정하면 processor는 그 범위의 memory를 가리키는 pointer 안의 tag와 application이 앞서 설정한 version을 비교합니다. 두 값이 일치할 때만 memory access를 허용하며, 일치하지 않으면 exception을 발생시킵니다.
ADI를 완전히 활성화하는 3단계
14-27task는 다음 절차를 수행해야 ADI를 완전히 활성화할 수 있습니다.
- user mode `PSTATE.mcde` bit를 설정합니다. 이 bit는 task 전체 address space에서 ADI를 켜거나 끄는 master switch입니다.
- ADI를 활성화할 address range에 대응하는 모든 TLB entry의 `TTE.mcd` bit를 설정합니다. MMU는 이 bit가 설정된 page에서만 version tag를 검사합니다.
- `stxa` instruction과 MCD 전용 ASI 중 하나를 사용해 virtual address의 version tag를 설정합니다. 각 `stxa`는 ADI block 하나의 byte 범위에 tag를 설정하므로 page 전체에 tag를 지정하려면 page 끝까지 반복해야 합니다.
ADI block과 virtual address tag bit
28-38platform의 ADI block size는 hypervisor가 machine description table을 통해 kernel에 제공합니다. hypervisor는 virtual address 최상위 bit 중 version tag로 사용하는 bit 수도 함께 제공합니다.
memory location에 설정한 version tag는 physical memory에 저장됩니다. MMU에 제시하는 virtual address의 ADI version tag bit에도 같은 값이 들어 있어야 합니다. SPARC M7에서는 MMU가 bits 63-60을 version tag로 사용하고 ADI block size는 cacheline과 같은 64 byte입니다. 예를 들어 memory range에 ADI version 10을 설정했다면 bits 63-60에 `0xa`가 든 virtual address로 접근해야 합니다.
mprotect()와 ADI auxiliary vector
39-54page 집합에는 `PROT_ADI` flag를 지정한 `mprotect()`로 ADI를 활성화합니다. task가 처음으로 page에 ADI를 활성화하면 kernel이 해당 task의 `PSTATE.mcde` bit를 설정합니다.
memory address의 version tag는 `ASI_MCD_PRIMARY` 또는 `ASI_MCD_ST_BLKINIT_PRIMARY`를 사용한 `stxa` instruction으로 설정합니다. hypervisor가 제공한 ADI block size와 기타 정보는 kernel이 auxiliary vector로 user space에 반환합니다.
| auxiliary vector | 의미 |
|---|---|
| `AT_ADI_BLKSZ` | ADI block size입니다. ADI versioning의 byte 단위 granularity이자 alignment입니다. |
| `AT_ADI_NBITS` | virtual address(VA)에서 ADI version에 사용하는 bit 수입니다. |
예약 tag와 physical page의 tag 저장
55-65ADI 사용 시 다음 사항을 유의해야 합니다.
- version tag 값 `0x0`과 `0xf`는 예약되어 있습니다. 두 값은 virtual address의 어떤 tag와도 일치하므로 mismatch exception을 만들지 않습니다.
- user space는 virtual address를 대상으로 version tag를 설정하지만 tag 자체는 physical memory에 저장됩니다. task에 physical page를 할당하고 PTE를 만든 뒤 그 page에 tag를 설정합니다.
page 해제·swap·migration에서의 tag
66-80task가 version tag를 설정했던 memory page를 해제하면 page는 free page pool로 돌아갑니다. 다른 task에 다시 할당할 때 kernel은 block initialization ASI로 page를 지우며 version tag도 함께 제거합니다. 같은 task가 그 page를 다시 받더라도 이전 tag는 남아 있지 않습니다.
non-faulting load에서는 ADI tag mismatch를 감지하지 않습니다. kernel은 user page에 tag를 설정하지 않으며 version tag 설정은 전적으로 task 책임입니다. 다만 page를 disk로 swap out했다가 다시 swap in하거나 page를 migrate할 때는 kernel이 version tag를 보존합니다.
page size와 writable memory 제약
81-90ADI는 모든 page size에서 동작하므로 user space task가 page size를 알 필요는 없습니다. virtual address range를 선택하고 `mprotect()`로 ADI를 활성화한 뒤 전체 range에 version tag를 설정하면 됩니다. `mprotect()`는 range가 page size에 맞춰 정렬되고 그 배수 크기인지 보장합니다.
ADI tag는 writable memory에만 설정할 수 있습니다. 예를 들어 read-only mapping에는 ADI tag를 설정할 수 없습니다.
disrupting memory corruption trap
91-116ADI가 활성화되면 새로운 trap이 발생할 수 있습니다. `TTE.mcd=1`인 memory location에 store하고 task가 `PSTATE.mcde=1`로 실행 중이며, 사용한 address의 ADI tag(bit 63:60)가 해당 cacheline tag와 다르면 memory corruption trap이 발생합니다.
기본값은 disrupting trap입니다. trap은 먼저 hypervisor로 전달되고, hypervisor는 sun4v error report를 만든 뒤 resumable error `TT=0x7e` trap을 kernel에 보냅니다. kernel은 원인이 된 task에 다음 정보와 함께 `SIGSEGV`를 전달합니다.
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIDERR;
siginfo.si_addr = addr; /* PC where first mismatch occurred */
siginfo.si_trapno = 0;
precise memory corruption trap
117-138같은 tag mismatch 조건에서 MCD precise exception이 활성화되어 `MCDPERR=1`이면 kernel에 `TT=0x1a` precise exception을 보냅니다. kernel은 원인이 된 task에 다음 `SIGSEGV` 정보를 전달합니다.
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ADIPERR;
siginfo.si_addr = addr; /* address that caused trap */
siginfo.si_trapno = 0;
load에서 발생한 ADI tag mismatch는 항상 precise trap으로 처리됩니다.
MCD disabled trap
139-155ADI를 활성화하지 않은 task가 memory address에 ADI version을 설정하려 하면 processor가 MCD disabled trap을 보냅니다. hypervisor가 먼저 처리한 뒤 fault type `0xa`(invalid ASI)인 Data Access Exception trap으로 kernel에 전달합니다.
kernel은 해당 task에 다음 정보와 함께 `SIGSEGV` signal을 보냅니다.
siginfo.si_signo = SIGSEGV;
siginfo.errno = 0;
siginfo.si_code = SEGV_ACCADI;
siginfo.si_addr = addr; /* address that caused trap */
siginfo.si_trapno = 0;
ADI 사용 예제 program
156-286다음 sample program은 auxiliary vector에서 `AT_ADI_BLKSZ`와 `AT_ADI_NBITS`를 읽고, shared memory를 만든 뒤 `PROT_ADI`로 보호 속성을 설정하는 전체 절차를 보여 줍니다.
program은 각 ADI block에 version 10을 기록하고, address 최상위 `adi_nbits`에 같은 version을 넣은 versioned address를 만들어 데이터를 쓰고 검증합니다. 마지막에는 `PROT_ADI`를 제거하고 shared memory를 정리합니다.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <asm/asi.h>
#ifndef AT_ADI_BLKSZ
#define AT_ADI_BLKSZ 48
#endif
#ifndef AT_ADI_NBITS
#define AT_ADI_NBITS 49
#endif
#ifndef PROT_ADI
#define PROT_ADI 0x10
#endif
#define BUFFER_SIZE 32*1024*1024UL
main(int argc, char* argv[], char* envp[])
{
unsigned long i, mcde, adi_blksz, adi_nbits;
char *shmaddr, *tmp_addr, *end, *veraddr, *clraddr;
int shmid, version;
Elf64_auxv_t *auxv;
adi_blksz = 0;
while(*envp++ != NULL);
for (auxv = (Elf64_auxv_t *)envp; auxv->a_type != AT_NULL; auxv++) {
switch (auxv->a_type) {
case AT_ADI_BLKSZ:
adi_blksz = auxv->a_un.a_val;
break;
case AT_ADI_NBITS:
adi_nbits = auxv->a_un.a_val;
break;
}
}
if (adi_blksz == 0) {
fprintf(stderr, "Oops! ADI is not supported\n");
exit(1);
}
printf("ADI capabilities:\n");
printf("\tBlock size = %ld\n", adi_blksz);
printf("\tNumber of bits = %ld\n", adi_nbits);
if ((shmid = shmget(2, BUFFER_SIZE,
IPC_CREAT | SHM_R | SHM_W)) < 0) {
perror("shmget failed");
exit(1);
}
shmaddr = shmat(shmid, NULL, 0);
if (shmaddr == (char *)-1) {
perror("shm attach failed");
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE|PROT_ADI)) {
perror("mprotect failed");
goto err_out;
}
/* Set the ADI version tag on the shm segment
*/
version = 10;
tmp_addr = shmaddr;
end = shmaddr + BUFFER_SIZE;
while (tmp_addr < end) {
asm volatile(
"stxa %1, [%0]0x90\n\t"
:
: "r" (tmp_addr), "r" (version));
tmp_addr += adi_blksz;
}
asm volatile("membar #Sync\n\t");
/* Create a versioned address from the normal address by placing
* version tag in the upper adi_nbits bits
*/
tmp_addr = (void *) ((unsigned long)shmaddr << adi_nbits);
tmp_addr = (void *) ((unsigned long)tmp_addr >> adi_nbits);
veraddr = (void *) (((unsigned long)version << (64-adi_nbits))
| (unsigned long)tmp_addr);
printf("Starting the writes:\n");
for (i = 0; i < BUFFER_SIZE; i++) {
veraddr[i] = (char)(i);
if (!(i % (1024 * 1024)))
printf(".");
}
printf("\n");
printf("Verifying data...");
fflush(stdout);
for (i = 0; i < BUFFER_SIZE; i++)
if (veraddr[i] != (char)i)
printf("\nIndex %lu mismatched\n", i);
printf("Done.\n");
/* Disable ADI and clean up
*/
if (mprotect(shmaddr, BUFFER_SIZE, PROT_READ|PROT_WRITE)) {
perror("mprotect failed");
goto err_out;
}
if (shmdt((const void *)shmaddr) != 0)
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(0);
err_out:
if (shmdt((const void *)shmaddr) != 0)
perror("Detach failure");
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
요약과 해설
adi.rst:1-286ADI는 pointer의 최상위 version bit와 physical memory에 저장된 tag를 비교해 잘못된 pointer 접근을 하드웨어에서 검출합니다. `PSTATE.mcde`, `TTE.mcd`, `PROT_ADI`가 각각 task·page·user API 수준의 활성화를 담당하며 block size와 tag bit 수는 auxiliary vector로 전달됩니다.
불일치 시 store는 기본적으로 disrupting 또는 선택적으로 precise trap을 만들고, load mismatch는 항상 precise합니다. kernel은 `SEGV_ADIDERR`, `SEGV_ADIPERR`, `SEGV_ACCADI`를 구분해 `SIGSEGV`로 보고하며 swap과 migration에서는 tag를 보존합니다.