Re: [RFC PATCH 4/6] uprobes/x86: Emulate rip-relative call's

From: Masami Hiramatsu
Date: Thu Apr 10 2014 - 22:39:05 EST


(2014/04/11 2:00), Oleg Nesterov wrote:
> On 04/10, Oleg Nesterov wrote:
>>
>> On 04/10, Masami Hiramatsu wrote:
>>>
>>> (2014/04/10 22:41), Denys Vlasenko wrote:
>>>> There is this monstrosity, "16-bit override for branches" in 64-mode:
>>>>
>>>> 66 e8 nn nn callw <offset16>
>>>>
>>>> Nobody sane uses it because it truncates instruction pointer.
>>>
>>> No problem, insn.c can handle that too. :)
>>
>> Does it?
>>
>> "callw 1f; 1:\n"
>> "rep; nop\n"
>>
>> objdump:
>>
>> 66 e8 00 00 callw 485 <_init-0x3ffed3>
>> f3 90 pause
>>
>>
>> if we probe this "callw", we copy MAX_INSN_BYTES into auprobe->insn,
>> and after insn_get_length() (insn_complete() == T)
>>
>> // this is correct
>> OPCODE1() == e8
>>
>> // this all looks wrong
>> insn->length == 6
>> insn->immediate.value == -1863122944
>> insn->immediate.nbytes == 4
>>
>> so it seems that lib/insn.c treats the next "pause" insn as the high
>> 16 bits of address.
>
> Or perhaps lib/insn.c is fine but objdump is wrong? And everything
> should work correctly? Although in this case I do not understand what
> this "callw" actually does.

Yeah, I think objdump is wrong. see below.

>
> int main(void)
> {
> asm (
> "nop\n"
>
> "callw 1f; 1:\n"
> ".byte 0\n"
> ".byte 0\n"
> );
>
> return 0;
> }
>
> this runs just fine. With or without gdb. And gdb shows that ->ip is
> incremented by 6 after "callw".
>
> int main(void)
> {
> asm (
> "nop\n"
>
> "callw 1f; 1:\n"
> ".byte 10\n"
> ".byte 20\n"
> );
>
> return 0;
> }
>
> objdump:
>
> 000000000040047c <main>:
> 40047c: 55 push %rbp
> 40047d: 48 89 e5 mov %rsp,%rbp
> 400480: 90 nop
> 400481: 66 e8 00 00 callw 485 <_init-0x3ffed3>
> 400485: 0a 14 b8 or (%rax,%rdi,4),%dl
> 400488: 00 00 add %al,(%rax)
> 40048a: 00 00 add %al,(%rax)
> 40048c: c9 leaveq
> 40048d: c3 retq
>
> run:
>
> $ ./t
> Segmentation fault (core dumped)
>
> $ gdb ./t core.*
> ...
> #0 0x00000000144a0487 in ?? ()
>
> 0x144a0487 - 0x400481 == 0x140a0006, this matches the additional 2 .bytes treated
> as offset.

Ah, OK. Ive also forgotten the operand size prefix on x86/x86-64.

The callw is actually "call with operand-size override prefix".
The operand-size prefix(0x66) changes the number of bytes (size) of
its operand (including immediate). But that depends on the opcode.

As I said, opcode 0xe8 (call) has Jz operand (jump offset immediate
with 16bit or 32bit size, depends on the operand size).
And on x86-32, the default operand-size is 4bytes, and it
changes to 2bytes with the 0x66 prefix. This means that the "66 e8"
becomes "callw" on x86-32.

However, please see Intel SDM 2b, appendix A,
A.2.5 Superscripts Utilized in Opcode Tables
----
f64
The operand size is forced to a 64-bit operand size when in 64-bit mode
(prefixes that change operand size are ignored for this instruction in 64-bit
mode
----
And Table A-2. One-byte Opcode Map: (08H — FFH)
-----
CALL(f64) Jz
-----

Ah, I got this. This means that "call" on x86-64 never be "callw",
because the operand-size always 64bit, and in that case, immediate
is 4 bytes(*).

(*) again, at A.2.2 Codes for Operand Type
---
z Word for 16-bit operand-size or doubleword for 32 or 64-bit operand-size.
---

So, at least for this case, objdump is wrong. lib/insn.c is correct. :)

Thank you,

--
Masami HIRAMATSU
Software Platform Research Dept. Linux Technology Center
Hitachi, Ltd., Yokohama Research Laboratory
E-mail: masami.hiramatsu.pt@xxxxxxxxxxx


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/