|
At the heart of the code for copying data between kernel and user-space on x86,
there is the macro __copy_user, which expands to some assembly as
follows :
#define __copy_user(to,from,size)
do {
int __d0, __d1;
__asm__ __volatile__(
"0: rep; movsl\n"
" movl %3,%0\n"
"1: rep; movsb\n"
"2:\n"
".section .fixup,\"ax\"\n"
"3: lea 0(%3,%0,4),%0\n"
" jmp 2b\n"
".previous\n"
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,3b\n"
" .long 1b,2b\n"
".previous"
: "=&c"(size), "=&D" (__d0), "=&S" (__d1)
: "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)
: "memory");
} while (0)
This is perhaps an intimidating few lines, so we here we go into more
detail into the code. Don't forget to read the inline assembly links on
this site.
Naive copy
"0: rep; movsl\n"
" movl %3,%0\n"
"1: rep; movsb\n"
"2:\n"
The first three lines naively copy
'size' bytes from 'from' to 'to'. I write "naively" because it might happen
that the copy fails; then a memory exception would occur. A memory fault
could occur for several reasons, each having their own way to handle the fault.
The extra code used in '__copy_user' is one way to handle simple errors.
Fixup
".section .fixup,\"ax\"\n"
"3: lea 0(%3,%0,4),%0\n"
" jmp 2b\n"
The kernel contains a table (in section '__ex_table') containing entries (X,
Y) saying that if an error occurs at address X, then jump to address Y. The
entries declared in the '__copy_user' code thus say that if a memory error
occurs when executing the code at the label '0:', the handler should return
to (and then execute) the code at the label '3:', calculating the actual
number of non-copied bytes. Likewise, an error at '1:' makes the
handler return to '2:', just skipping any more copying.
Note that lea 0(%3,%0,4),%0 is equivalent to the calculation
%ecx = (size % 4) + %ecx * 4.
Labels like '0:', '1:', ... are "local labels", which can be used several
times for different locations in the same code. They are then referenced by
'0b', '1f', ... meaning "label 0 searching backwards" and "label 1 searching
forwards" respectively.
Sections
".previous\n"
The assembler directive .previous just tells the assembler to put the
following code/data in the section used before the current section, probably
the .text section.
The exception table data is put into the __ex_table section used by all exception
table code in the kernel.
Exception table
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,3b\n"
" .long 1b,2b\n"
".previous"
The actual exception table itself in X,Y form, as described above.
Originally by Per Persson, modified by John Levon.
| |