Skip to content

Commit 83fe01e

Browse files
alexcrichtonabrown
andauthored
x64: Fix encoding of RIP-relative addressing (#10819)
* x64: Fix encoding of RIP-relative addressing This commit fixes a bug in the new assembler which was surfaced through the changes in #10782 but was a pre-existing issue. Specifically the encoding of a RIP-relative addressing mode required knowing the number of bytes at the end of an instruction but this was accidentally hardcoded to 0. In #10782 `imul` instructions were added where a RIP-relative address mode can be used in conjunction with an immediate which cause the RIP-relative addressing to load from the wrong address. This bug can in theory affect other instructions in the new assembler as well, but auditing the list of instructions it looks like `imul` is the only one that can possibly have an immediate after a RIP-relative addressing mode. That means that prior instructions using the new assembler should not be affected. * Fix testing the assembler crate * Apply suggestions from code review Co-authored-by: Andrew Brown <[email protected]> --------- Co-authored-by: Andrew Brown <[email protected]>
1 parent ee9c795 commit 83fe01e

File tree

4 files changed

+164
-6
lines changed

4 files changed

+164
-6
lines changed

cranelift/assembler-x64/meta/src/generate/format.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,10 @@ impl dsl::Format {
148148
f.empty_line();
149149
f.comment("Emit ModR/M byte.");
150150
}
151+
let bytes_at_end = match self.operands_by_kind().as_slice() {
152+
[.., Imm(imm)] => imm.bytes(),
153+
_ => 0,
154+
};
151155

152156
match self.operands_by_kind().as_slice() {
153157
[FixedReg(_)] | [FixedReg(_), FixedReg(_)] | [FixedReg(_), Imm(_)] => {
@@ -166,7 +170,10 @@ impl dsl::Format {
166170
| [FixedReg(_), FixedReg(_), RegMem(mem)] => {
167171
let digit = rex.digit.unwrap();
168172
fmtln!(f, "let digit = 0x{digit:x};");
169-
fmtln!(f, "self.{mem}.encode_rex_suffixes(buf, off, digit, 0);");
173+
fmtln!(
174+
f,
175+
"self.{mem}.encode_rex_suffixes(buf, off, digit, {bytes_at_end});"
176+
);
170177
}
171178
[Reg(reg), RegMem(mem)]
172179
| [Reg(reg), RegMem(mem), Imm(_)]
@@ -175,7 +182,10 @@ impl dsl::Format {
175182
| [RegMem(mem), Reg(reg), Imm(_)]
176183
| [RegMem(mem), Reg(reg), FixedReg(_)] => {
177184
fmtln!(f, "let reg = self.{reg}.enc();");
178-
fmtln!(f, "self.{mem}.encode_rex_suffixes(buf, off, reg, 0);");
185+
fmtln!(
186+
f,
187+
"self.{mem}.encode_rex_suffixes(buf, off, reg, {bytes_at_end});"
188+
);
179189
}
180190
unknown => unimplemented!("unknown pattern: {unknown:?}"),
181191
}

cranelift/assembler-x64/src/fuzz.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! unconditionally (use the `fuzz` feature instead).
66
77
use crate::{
8-
AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, Fixed, Gpr, Inst, NonRspGpr, Registers, Xmm,
8+
AmodeOffset, AmodeOffsetPlusKnownOffset, AsReg, CodeSink, Constant, Fixed, Gpr, Inst, Label,
9+
NonRspGpr, Registers, TrapCode, Xmm,
910
};
1011
use arbitrary::{Arbitrary, Result, Unstructured};
1112
use capstone::{Capstone, arch::BuildsCapstone, arch::BuildsCapstoneSyntax, arch::x86};
@@ -42,10 +43,75 @@ pub fn roundtrip(inst: &Inst<FuzzRegs>) {
4243
/// This will skip any traps or label registrations, but this is fine for the
4344
/// single-instruction disassembly we're doing here.
4445
fn assemble(inst: &Inst<FuzzRegs>) -> Vec<u8> {
45-
let mut buffer = Vec::new();
46+
let mut sink = TestCodeSink::default();
4647
let offsets: Vec<i32> = Vec::new();
47-
inst.encode(&mut buffer, &offsets);
48-
buffer
48+
inst.encode(&mut sink, &offsets);
49+
sink.patch_labels_as_if_they_referred_to_end();
50+
sink.buf
51+
}
52+
53+
#[derive(Default)]
54+
struct TestCodeSink {
55+
buf: Vec<u8>,
56+
offsets_using_label: Vec<u32>,
57+
}
58+
59+
impl TestCodeSink {
60+
/// References to labels, e.g. RIP-relative addressing, is stored with an
61+
/// adjustment that takes into account the distance from the relative offset
62+
/// to the end of the instruction, where the offset is relative to. That
63+
/// means that to indeed make the offset relative to the end of the
64+
/// instruction, which is what we pretend all labels are bound to, it's
65+
/// required that this adjustment is taken into account.
66+
///
67+
/// This function will iterate over all labels bound to this code sink and
68+
/// pretend the label is found at the end of the `buf`. That means that the
69+
/// distance from the label to the end of `buf` minus 4, which is the width
70+
/// of the offset, is added to what's already present in the encoding buffer.
71+
///
72+
/// This is effectively undoing the `bytes_at_end` adjustment that's part of
73+
/// `Amode::RipRelative` addressing.
74+
fn patch_labels_as_if_they_referred_to_end(&mut self) {
75+
let len = i32::try_from(self.buf.len()).unwrap();
76+
for offset in self.offsets_using_label.iter() {
77+
let range = self.buf[*offset as usize..].first_chunk_mut::<4>().unwrap();
78+
let offset = i32::try_from(*offset).unwrap() + 4;
79+
let rel_distance = len - offset;
80+
*range = (i32::from_le_bytes(*range) + rel_distance).to_le_bytes();
81+
}
82+
}
83+
}
84+
85+
impl CodeSink for TestCodeSink {
86+
fn put1(&mut self, v: u8) {
87+
self.buf.extend_from_slice(&[v]);
88+
}
89+
90+
fn put2(&mut self, v: u16) {
91+
self.buf.extend_from_slice(&v.to_le_bytes());
92+
}
93+
94+
fn put4(&mut self, v: u32) {
95+
self.buf.extend_from_slice(&v.to_le_bytes());
96+
}
97+
98+
fn put8(&mut self, v: u64) {
99+
self.buf.extend_from_slice(&v.to_le_bytes());
100+
}
101+
102+
fn add_trap(&mut self, _: TrapCode) {}
103+
104+
fn current_offset(&self) -> u32 {
105+
self.buf.len().try_into().unwrap()
106+
}
107+
108+
fn use_label_at_offset(&mut self, offset: u32, _: Label) {
109+
self.offsets_using_label.push(offset);
110+
}
111+
112+
fn get_label_for_constant(&mut self, c: Constant) -> Label {
113+
Label(c.0)
114+
}
49115
}
50116

51117
/// Building a new `Capstone` each time is suboptimal (TODO).

cranelift/filetests/filetests/isa/x64/mul.clif

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,3 +622,67 @@ block0(v0: i8, v1: i8):
622622
; popq %rbp
623623
; retq
624624

625+
function %imul_and_constant_pool_small_immediate() -> i32 {
626+
block0:
627+
v0 = iconst.i32 7
628+
v1 = iconst.i32 0x22222222
629+
v2 = imul v0, v1
630+
return v2
631+
}
632+
633+
; VCode:
634+
; pushq %rbp
635+
; movq %rsp, %rbp
636+
; block0:
637+
; imull $0x7, (%rip), %eax
638+
; movq %rbp, %rsp
639+
; popq %rbp
640+
; ret
641+
;
642+
; Disassembled:
643+
; block0: ; offset 0x0
644+
; pushq %rbp
645+
; movq %rsp, %rbp
646+
; block1: ; offset 0x4
647+
; imull $7, 5(%rip), %eax
648+
; movq %rbp, %rsp
649+
; popq %rbp
650+
; retq
651+
; andb (%rdx), %ah
652+
; andb (%rdx), %ah
653+
; addb %al, (%rax)
654+
; addb %al, (%rax)
655+
656+
function %imul_and_constant_pool_big_immediate() -> i32 {
657+
block0:
658+
v0 = iconst.i32 0x11111111
659+
v1 = iconst.i32 0x22222222
660+
v2 = imul v0, v1
661+
return v2
662+
}
663+
664+
; VCode:
665+
; pushq %rbp
666+
; movq %rsp, %rbp
667+
; block0:
668+
; imull $0x11111111, (%rip), %eax
669+
; movq %rbp, %rsp
670+
; popq %rbp
671+
; ret
672+
;
673+
; Disassembled:
674+
; block0: ; offset 0x0
675+
; pushq %rbp
676+
; movq %rsp, %rbp
677+
; block1: ; offset 0x4
678+
; imull $0x11111111, 0xa(%rip), %eax
679+
; movq %rbp, %rsp
680+
; popq %rbp
681+
; retq
682+
; addb %al, (%rax)
683+
; addb %al, (%rax)
684+
; addb %ah, (%rdx)
685+
; andb (%rdx), %ah
686+
; andb (%rax), %al
687+
; addb %al, (%rax)
688+

cranelift/filetests/filetests/runtests/arithmetic.clif

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,3 +556,21 @@ block0(v0: i8):
556556
; run: %udiv_i8_const(0) == 0
557557
; run: %udiv_i8_const(-1) == 1
558558
; run: %udiv_i8_const(0xFE) == 1
559+
560+
function %imul_small_constant() -> i32 {
561+
block0:
562+
v0 = iconst.i32 7
563+
v1 = iconst.i32 0x22222222
564+
v2 = imul v0, v1
565+
return v2
566+
}
567+
; run: %imul_small_constant() == -286331154
568+
569+
function %imul_big_constant() -> i32 {
570+
block0:
571+
v0 = iconst.i32 0x11111111
572+
v1 = iconst.i32 0x22222222
573+
v2 = imul v0, v1
574+
return v2
575+
}
576+
; run: %imul_big_constant() == 248153666

0 commit comments

Comments
 (0)