From: Paolo Bonzini <pbonzini@redhat.com>
To: Bill Wendling <morbo@google.com>,
kvm@vger.kernel.org, jmattson@google.com
Cc: thuth@redhat.com, alexandru.elisei@arm.com
Subject: Re: [kvm-unit-tests PATCH v3 2/2] x86: realmode: initialize inregs with a stack
Date: Mon, 4 Nov 2019 13:09:29 +0100 [thread overview]
Message-ID: <d356414e-e2d5-68db-5e58-1ac63f56b0e4@redhat.com> (raw)
In-Reply-To: <20191101203353.150049-3-morbo@google.com>
On 01/11/19 21:33, Bill Wendling wrote:
> Tests may use the stack during execution. It's easy to miss allocating
> one, so automatically point %esp to the stack when initializing
> "inregs". Also remove the initialization of ".esp" in "test_movzx_movsx"
> as it doesn't appear to be required.
>
> Signed-off-by: Bill Wendling <morbo@google.com>
> ---
> x86/realmode.c | 158 +++++++++++++++++++++++++++++--------------------
> 1 file changed, 95 insertions(+), 63 deletions(-)
>
> diff --git a/x86/realmode.c b/x86/realmode.c
> index 629a221..f5967ef 100644
> --- a/x86/realmode.c
> +++ b/x86/realmode.c
> @@ -11,6 +11,10 @@ typedef unsigned short u16;
> typedef unsigned u32;
> typedef unsigned long long u64;
>
> +#ifndef NULL
> +#define NULL ((void*)0)
> +#endif
> +
> void realmode_start(void);
> void test_function(void);
>
> @@ -140,8 +144,22 @@ struct insn_desc {
> u16 len;
> };
>
> +struct {
> + u32 stack[128];
> + char top[];
> +} tmp_stack;
> +
> static struct regs inregs, outregs;
>
> +static inline void init_inregs(struct regs *regs)
> +{
> + inregs = (struct regs){ 0 };
> + if (regs)
> + inregs = *regs;
> + if (!inregs.esp)
> + inregs.esp = (unsigned long)&tmp_stack.top;
> +}
> +
> static void exec_in_big_real_mode(struct insn_desc *insn)
> {
> unsigned long tmp;
> @@ -302,7 +320,8 @@ static void test_shld(void)
> {
> MK_INSN(shld_test, "shld $8,%edx,%eax\n\t");
>
> - inregs = (struct regs){ .eax = 0xbe, .edx = 0xef000000 };
> + init_inregs(&(struct regs){ .eax = 0xbe, .edx = 0xef000000 });
> +
> exec_in_big_real_mode(&insn_shld_test);
> report("shld", ~0, outregs.eax == 0xbeef);
> }
> @@ -315,7 +334,7 @@ static void test_mov_imm(void)
> MK_INSN(mov_r8_imm_2, "mov $0x34, %al");
> MK_INSN(mov_r8_imm_3, "mov $0x12, %ah\n\t" "mov $0x34, %al\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_mov_r16_imm_1);
> report("mov 1", R_AX, outregs.eax == 1234);
> @@ -342,7 +361,7 @@ static void test_sub_imm(void)
> MK_INSN(sub_r8_imm_1, "mov $0x12, %ah\n\t" "sub $0x10, %ah\n\t");
> MK_INSN(sub_r8_imm_2, "mov $0x34, %al\n\t" "sub $0x10, %al\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_sub_r16_imm_1);
> report("sub 1", R_AX, outregs.eax == 1224);
> @@ -366,7 +385,7 @@ static void test_xor_imm(void)
> MK_INSN(xor_r8_imm_1, "mov $0x12, %ah\n\t" "xor $0x12, %ah\n\t");
> MK_INSN(xor_r8_imm_2, "mov $0x34, %al\n\t" "xor $0x34, %al\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_xor_r16_imm_1);
> report("xor 1", R_AX, outregs.eax == 0);
> @@ -392,7 +411,7 @@ static void test_cmp_imm(void)
> MK_INSN(cmp_test3, "mov $0x34, %al\n\t"
> "cmp $0x24, %al\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> /* test cmp imm8 with AL */
> /* ZF: (bit 6) Zero Flag becomes 1 if an operation results
> @@ -415,7 +434,7 @@ static void test_add_imm(void)
> MK_INSN(add_test2, "mov $0x12, %eax \n\t"
> "add $0x21, %al\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_add_test1);
> report("add 1", ~0, outregs.eax == 0x55555555);
> @@ -433,7 +452,7 @@ static void test_eflags_insn(void)
> MK_INSN(cld, "cld");
> MK_INSN(std, "std");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_clc);
> report("clc", ~0, (outregs.eflags & 1) == 0);
> @@ -484,7 +503,7 @@ static void test_io(void)
> "mov $0x00000000, %eax \n\t"
> "in %dx, %eax \n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_io_test1);
> report("pio 1", R_AX, outregs.eax == 0xff);
> @@ -513,12 +532,8 @@ extern void retf_imm(void);
>
> static void test_call(void)
> {
> - u32 esp[16];
> u32 addr;
>
> - inregs = (struct regs){ 0 };
> - inregs.esp = (u32)&esp[ARRAY_SIZE(esp)];
> -
> MK_INSN(call1, "mov $test_function, %eax \n\t"
> "call *%eax\n\t");
> MK_INSN(call_near1, "jmp 2f\n\t"
> @@ -535,6 +550,8 @@ static void test_call(void)
> MK_INSN(ret_imm, "sub $10, %sp; jmp 2f; 1: retw $10; 2: callw 1b");
> MK_INSN(retf_imm, "sub $10, %sp; lcallw $0, $retf_imm");
>
> + init_inregs(NULL);
> +
> exec_in_big_real_mode(&insn_call1);
> report("call 1", R_AX, outregs.eax == 0x1234);
>
> @@ -572,7 +589,7 @@ static void test_jcc_short(void)
> "mov $0x1234, %eax\n\t"
> "1:\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_jnz_short1);
> report("jnz short 1", ~0, 1);
> @@ -595,7 +612,7 @@ static void test_jcc_near(void)
> MK_INSN(jmp_near1, ".byte 0xE9, 0x06, 0x00\n\t"
> "mov $0x1234, %eax\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_jnz_near1);
> report("jnz near 1", 0, 1);
> @@ -609,14 +626,13 @@ static void test_jcc_near(void)
>
> static void test_long_jmp(void)
> {
> - u32 esp[16];
> -
> - inregs = (struct regs){ 0 };
> - inregs.esp = (u32)&esp[ARRAY_SIZE(esp)];
> MK_INSN(long_jmp, "call 1f\n\t"
> "jmp 2f\n\t"
> "1: jmp $0, $test_function\n\t"
> "2:\n\t");
> +
> + init_inregs(NULL);
> +
> exec_in_big_real_mode(&insn_long_jmp);
> report("jmp far 1", R_AX, outregs.eax == 0x1234);
> }
> @@ -658,7 +674,7 @@ static void test_push_pop(void)
> "xor $0x12340000, %esp \n\t"
> "pop %bx");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_push32);
> report("push/pop 1", R_AX|R_BX,
> @@ -691,17 +707,12 @@ static void test_null(void)
> {
> MK_INSN(null, "");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_null);
> report("null", 0, 1);
> }
>
> -struct {
> - char stack[500];
> - char top[];
> -} tmp_stack;
> -
> static void test_pusha_popa(void)
> {
> MK_INSN(pusha, "pusha\n\t"
> @@ -726,7 +737,7 @@ static void test_pusha_popa(void)
> "popa\n\t"
> );
>
> - inregs = (struct regs){ .eax = 0, .ebx = 1, .ecx = 2, .edx = 3, .esi = 4, .edi = 5, .ebp = 6, .esp = (unsigned long)&tmp_stack.top };
> + init_inregs(&(struct regs){ .eax = 0, .ebx = 1, .ecx = 2, .edx = 3, .esi = 4, .edi = 5, .ebp = 6 });
>
> exec_in_big_real_mode(&insn_pusha);
> report("pusha/popa 1", 0, 1);
> @@ -774,7 +785,7 @@ static void test_iret(void)
> "1: iretw\n\t"
> "2:\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_iret32);
> report("iret 1", 0, 1);
> @@ -792,7 +803,7 @@ static void test_iret(void)
>
> static void test_int(void)
> {
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> *(u32 *)(0x11 * 4) = 0x1000; /* Store a pointer to address 0x1000 in IDT entry 0x11 */
> *(u8 *)(0x1000) = 0xcf; /* 0x1000 contains an IRET instruction */
> @@ -829,7 +840,7 @@ static void test_imul(void)
> "mov $4, %ecx\n\t"
> "imul %ecx\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_imul8_1);
> report("imul 1", R_AX | R_CX | R_DX, (outregs.eax & 0xff) == (u8)-8);
> @@ -866,7 +877,7 @@ static void test_mul(void)
> "mov $4, %ecx\n\t"
> "imul %ecx\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_mul8);
> report("mul 1", R_AX | R_CX | R_DX, (outregs.eax & 0xff) == 8);
> @@ -892,7 +903,7 @@ static void test_div(void)
> "mov $5, %ecx\n\t"
> "div %ecx\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_div8);
> report("div 1", R_AX | R_CX | R_DX, outregs.eax == 384);
> @@ -920,7 +931,7 @@ static void test_idiv(void)
> "mov $-2, %ecx\n\t"
> "idiv %ecx\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_idiv8);
> report("idiv 1", R_AX | R_CX | R_DX, outregs.eax == (u8)-128);
> @@ -939,7 +950,7 @@ static void test_cbw(void)
> MK_INSN(cwde, "mov $0xFFFE, %eax \n\t"
> "cwde\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_cbw);
> report("cbq 1", ~0, outregs.eax == 0xFFFE);
> @@ -964,7 +975,7 @@ static void test_loopcc(void)
> "1: dec %eax\n\t"
> "loopne 1b\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_loop);
> report("LOOPcc short 1", R_AX, outregs.eax == 10);
> @@ -1243,7 +1254,7 @@ static void test_das(void)
>
> MK_INSN(das, "das");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> for (i = 0; i < 1024; ++i) {
> unsigned tmp = test_cases[i];
> @@ -1278,7 +1289,7 @@ static void test_cwd_cdq(void)
> MK_INSN(cdq_2, "mov $0x10000000, %eax\n\t"
> "cdq\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_cwd_1);
> report("cwd 1", R_AX | R_DX,
> @@ -1307,7 +1318,7 @@ static struct {
>
> static void test_lds_lss(void)
> {
> - inregs = (struct regs){ .ebx = (unsigned long)&desc };
> + init_inregs(&(struct regs){ .ebx = (unsigned long)&desc });
>
> MK_INSN(lds, "push %ds\n\t"
> "lds (%ebx), %eax\n\t"
> @@ -1376,7 +1387,7 @@ static void test_jcxz(void)
> "mov $0, %ecx\n\t"
> "1:\n\t");
>
> - inregs = (struct regs){ 0 };
> + init_inregs(NULL);
>
> exec_in_big_real_mode(&insn_jcxz1);
> report("jcxz short 1", 0, 1);
> @@ -1400,8 +1411,10 @@ static void test_cpuid(void)
> unsigned function = 0x1234;
> unsigned eax, ebx, ecx, edx;
>
> - inregs.eax = eax = function;
> - inregs.ecx = ecx = 0;
> + init_inregs(&(struct regs){ .eax = function });
> +
> + eax = inregs.eax;
> + ecx = inregs.ecx;
> asm("cpuid" : "+a"(eax), "=b"(ebx), "+c"(ecx), "=d"(edx));
> exec_in_big_real_mode(&insn_cpuid);
> report("cpuid", R_AX|R_BX|R_CX|R_DX,
> @@ -1415,10 +1428,11 @@ static void test_ss_base_for_esp_ebp(void)
> MK_INSN(ssrel2, "mov %ss, %ax; mov %bx, %ss; movl (%ebp,%edi,8), %ebx; mov %ax, %ss");
> static unsigned array[] = { 0x12345678, 0, 0, 0, 0x87654321 };
>
> - inregs.ebx = 1;
> - inregs.ebp = (unsigned)array;
> + init_inregs(&(struct regs){ .ebx = 1, .ebp = (unsigned)array });
> +
> exec_in_big_real_mode(&insn_ssrel1);
> report("ss relative addressing (1)", R_AX | R_BX, outregs.ebx == 0x87654321);
> +
> inregs.ebx = 1;
> inregs.ebp = (unsigned)array;
> inregs.edi = 0;
> @@ -1434,7 +1448,8 @@ static void test_sgdt_sidt(void)
> MK_INSN(sidt, "sidtw (%eax)");
> struct table_descr x, y;
>
> - inregs.eax = (unsigned)&y;
> + init_inregs(&(struct regs){ .eax = (unsigned)&y });
> +
> asm volatile("sgdtw %0" : "=m"(x));
> exec_in_big_real_mode(&insn_sgdt);
> report("sgdt", 0, x.limit == y.limit && x.base == y.base);
> @@ -1449,7 +1464,8 @@ static void test_sahf(void)
> {
> MK_INSN(sahf, "sahf; pushfw; mov (%esp), %al; popfw");
>
> - inregs.eax = 0xfd00;
> + init_inregs(&(struct regs){ .eax = 0xfd00 });
> +
> exec_in_big_real_mode(&insn_sahf);
> report("sahf", R_AX, outregs.eax == (inregs.eax | 0xd7));
> }
> @@ -1458,7 +1474,8 @@ static void test_lahf(void)
> {
> MK_INSN(lahf, "pushfw; mov %al, (%esp); popfw; lahf");
>
> - inregs.eax = 0xc7;
> + init_inregs(&(struct regs){ .eax = 0xc7 });
> +
> exec_in_big_real_mode(&insn_lahf);
> report("lahf", R_AX, (outregs.eax >> 8) == inregs.eax);
> }
> @@ -1470,8 +1487,8 @@ static void test_movzx_movsx(void)
> MK_INSN(movzsah, "movsx %ah, %ebx");
> MK_INSN(movzxah, "movzx %ah, %ebx");
>
> - inregs.eax = 0x1234569c;
> - inregs.esp = 0xffff;
> + init_inregs(&(struct regs){ .eax = 0x1234569c });
> +
> exec_in_big_real_mode(&insn_movsx);
> report("movsx", R_BX, outregs.ebx == (signed char)inregs.eax);
> exec_in_big_real_mode(&insn_movzx);
> @@ -1486,7 +1503,8 @@ static void test_bswap(void)
> {
> MK_INSN(bswap, "bswap %ecx");
>
> - inregs.ecx = 0x12345678;
> + init_inregs(&(struct regs){ .ecx = 0x12345678 });
> +
> exec_in_big_real_mode(&insn_bswap);
> report("bswap", R_CX, outregs.ecx == 0x78563412);
> }
> @@ -1495,7 +1513,8 @@ static void test_aad(void)
> {
> MK_INSN(aad, "aad");
>
> - inregs.eax = 0x12345678;
> + init_inregs(&(struct regs){ .eax = 0x12345678 });
> +
> exec_in_big_real_mode(&insn_aad);
> report("aad", R_AX, outregs.eax == 0x123400d4);
> }
> @@ -1504,7 +1523,8 @@ static void test_aam(void)
> {
> MK_INSN(aam, "aam");
>
> - inregs.eax = 0x76543210;
> + init_inregs(&(struct regs){ .eax = 0x76543210 });
> +
> exec_in_big_real_mode(&insn_aam);
> report("aam", R_AX, outregs.eax == 0x76540106);
> }
> @@ -1519,8 +1539,8 @@ static void test_xlat(void)
> table[i] = i + 1;
> }
>
> - inregs.eax = 0x89abcdef;
> - inregs.ebx = (u32)table;
> + init_inregs(&(struct regs){ .eax = 0x89abcdef, .ebx = (u32)table });
> +
> exec_in_big_real_mode(&insn_xlat);
> report("xlat", R_AX, outregs.eax == 0x89abcdf0);
> }
> @@ -1530,7 +1550,8 @@ static void test_salc(void)
> MK_INSN(clc_salc, "clc; .byte 0xd6");
> MK_INSN(stc_salc, "stc; .byte 0xd6");
>
> - inregs.eax = 0x12345678;
> + init_inregs(&(struct regs){ .eax = 0x12345678 });
> +
> exec_in_big_real_mode(&insn_clc_salc);
> report("salc (1)", R_AX, outregs.eax == 0x12345600);
> exec_in_big_real_mode(&insn_stc_salc);
> @@ -1542,8 +1563,7 @@ static void test_fninit(void)
> u16 fcw = -1, fsw = -1;
> MK_INSN(fninit, "fninit ; fnstsw (%eax) ; fnstcw (%ebx)");
>
> - inregs.eax = (u32)&fsw;
> - inregs.ebx = (u32)&fcw;
> + init_inregs(&(struct regs){ .eax = (u32)&fsw, .ebx = (u32)&fcw });
>
> exec_in_big_real_mode(&insn_fninit);
> report("fninit", 0, fsw == 0 && (fcw & 0x103f) == 0x003f);
> @@ -1576,7 +1596,8 @@ static u32 cycles_in_big_real_mode(struct insn_desc *insn)
> {
> u64 start, end;
>
> - inregs.ecx = PERF_COUNT;
> + init_inregs(&(struct regs){ .ecx = PERF_COUNT });
> +
> exec_in_big_real_mode(insn);
> start = ((u64)outregs.esi << 32) | outregs.ebx;
> end = ((u64)outregs.edx << 32) | outregs.eax;
> @@ -1624,7 +1645,9 @@ static void test_perf_memory_load(void)
> u32 cyc, tmp;
>
> MK_INSN_PERF(perf_memory_load, "cmp $0, (%edi)");
> - inregs.edi = (u32)&tmp;
> +
> + init_inregs(&(struct regs){ .edi = (u32)&tmp });
> +
> cyc = cycles_in_big_real_mode(&insn_perf_memory_load);
> print_serial_u32((cyc - perf_baseline) / PERF_COUNT);
> print_serial(" cycles/emulated memory load instruction\n");
> @@ -1635,7 +1658,9 @@ static void test_perf_memory_store(void)
> u32 cyc, tmp;
>
> MK_INSN_PERF(perf_memory_store, "mov %ax, (%edi)");
> - inregs.edi = (u32)&tmp;
> +
> + init_inregs(&(struct regs){ .edi = (u32)&tmp });
> +
> cyc = cycles_in_big_real_mode(&insn_perf_memory_store);
> print_serial_u32((cyc - perf_baseline) / PERF_COUNT);
> print_serial(" cycles/emulated memory store instruction\n");
> @@ -1646,7 +1671,9 @@ static void test_perf_memory_rmw(void)
> u32 cyc, tmp;
>
> MK_INSN_PERF(perf_memory_rmw, "add $1, (%edi)");
> - inregs.edi = (u32)&tmp;
> +
> + init_inregs(&(struct regs){ .edi = (u32)&tmp });
> +
> cyc = cycles_in_big_real_mode(&insn_perf_memory_rmw);
> print_serial_u32((cyc - perf_baseline) / PERF_COUNT);
> print_serial(" cycles/emulated memory RMW instruction\n");
> @@ -1656,8 +1683,9 @@ static void test_dr_mod(void)
> {
> MK_INSN(drmod, "movl %ebx, %dr0\n\t"
> ".byte 0x0f \n\t .byte 0x21 \n\t .byte 0x0\n\t");
> - inregs.eax = 0xdead;
> - inregs.ebx = 0xaced;
> +
> + init_inregs(&(struct regs){ .eax = 0xdead, .ebx = 0xaced });
> +
> exec_in_big_real_mode(&insn_drmod);
> report("mov dr with mod bits", R_AX | R_BX, outregs.eax == 0xaced);
> }
> @@ -1670,7 +1698,9 @@ static void test_smsw(void)
> "movl %ebx, %cr0\n\t"
> "smswl %eax\n\t"
> "movl %ecx, %cr0\n\t");
> - inregs.eax = 0x12345678;
> +
> + init_inregs(&(struct regs){ .eax = 0x12345678 });
> +
> exec_in_big_real_mode(&insn_smsw);
> report("smsw", R_AX | R_BX | R_CX, outregs.eax == outregs.ebx);
> }
> @@ -1678,7 +1708,9 @@ static void test_smsw(void)
> static void test_xadd(void)
> {
> MK_INSN(xadd, "xaddl %eax, %eax\n\t");
> - inregs.eax = 0x12345678;
> +
> + init_inregs(&(struct regs){ .eax = 0x12345678 });
> +
> exec_in_big_real_mode(&insn_xadd);
> report("xadd", R_AX, outregs.eax == inregs.eax * 2);
> }
>
Applied, thanks.
Paolo
prev parent reply other threads:[~2019-11-04 12:09 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-11-01 20:33 [kvm-unit-tests PATCH v3 0/2] Save/Restore clobbered register and struct initialization Bill Wendling
2019-11-01 20:33 ` [kvm-unit-tests PATCH v3 1/2] x86: realmode: save and restore %es Bill Wendling
2019-11-04 12:08 ` Paolo Bonzini
2019-11-05 18:07 ` Jim Mattson
2019-11-05 22:26 ` Paolo Bonzini
2019-11-01 20:33 ` [kvm-unit-tests PATCH v3 2/2] x86: realmode: initialize inregs with a stack Bill Wendling
2019-11-04 12:09 ` Paolo Bonzini [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=d356414e-e2d5-68db-5e58-1ac63f56b0e4@redhat.com \
--to=pbonzini@redhat.com \
--cc=alexandru.elisei@arm.com \
--cc=jmattson@google.com \
--cc=kvm@vger.kernel.org \
--cc=morbo@google.com \
--cc=thuth@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).