รหัสเครื่อง x86_64 ขนาด 4 ไบต์
คำสั่ง BSF (บิตสแกนไปข้างหน้า) ทำสิ่งนี้อย่างแน่นอน !
0x0f 0xbc 0xc7 0xc3
ในการประกอบแบบ gcc นี่คือ:
.globl f
f:
bsfl %edi, %eax
ret
อินพุตจะถูกกำหนดในการลงทะเบียน EDI และส่งคืนในการลงทะเบียน EAX ตามมาตรฐานการเรียกแบบ64 บิตค
เนื่องจากการเข้ารหัสแบบสองส่วนเสริมของทั้งสองวิธีนี้ทำงานสำหรับ -ve เช่นเดียวกับ + ve ตัวเลข
นอกจากนี้แม้จะมีเอกสารอธิบายว่า"ถ้าเนื้อหาของตัวถูกดำเนินการต้นทางเป็น 0 เนื้อหาของตัวถูกดำเนินการปลายทางจะไม่ได้กำหนด" ฉันพบ Ubuntu VM ของฉันว่าผลลัพธ์ของf(0)
เป็น 0
คำแนะนำ:
- บันทึกข้างต้นเป็น
evenness.s
และประกอบกับgcc -c evenness.s -o evenness.o
- บันทึกไดรเวอร์ทดสอบต่อไปนี้เป็น
evenness-main.c
และคอมไพล์ด้วยgcc -c evenness-main.c -o evenness-main.o
:
#include <stdio.h>
extern int f(int n);
int main (int argc, char **argv) {
int i;
int testcases[] = { 14, 20, 94208, 7, 0, -4 };
for (i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) {
printf("%d, %d\n", testcases[i], f(testcases[i]));
}
return 0;
}
แล้ว:
- Link:
gcc evenness-main.o evenness.o -o evenness
- วิ่ง:
./evenness
@FarazMasroor ขอรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการได้รับคำตอบนี้
ฉันคุ้นเคยกับcมากกว่าความซับซ้อนของชุดประกอบ x86 ดังนั้นโดยทั่วไปฉันจะใช้คอมไพเลอร์เพื่อสร้างรหัสชุดประกอบสำหรับฉัน ฉันรู้จากประสบการณ์ว่าส่วนขยาย GCC เช่น__builtin_ffs()
, __builtin_ctz()
และ__builtin_popcount()
มักจะรวบรวมและประกอบไป 1 หรือ 2 คำแนะนำเกี่ยวกับ x86 ดังนั้นฉันเริ่มจากฟังก์ชั่นcเช่น:
int f(int n) {
return __builtin_ctz(n);
}
แทนการใช้ GCC รวบรวมปกติทุกวิธีการรหัสวัตถุ, คุณสามารถใช้-S
ตัวเลือกในการรวบรวมเพียงเพื่อประกอบ gcc -S -c evenness.c
- สิ่งนี้จะให้ไฟล์ประกอบevenness.s
ดังต่อไปนี้:
.file "evenness.c"
.text
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
rep bsfl %eax, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
.section .note.GNU-stack,"",@progbits
สิ่งนี้สามารถเล่นกอล์ฟได้มาก โดยเฉพาะอย่างยิ่งเรารู้ว่าอนุสัญญาการเรียกc สำหรับฟังก์ชั่นที่มีลายเซ็นดีและเรียบง่าย - พารามิเตอร์การป้อนข้อมูลจะถูกส่งผ่านในการลงทะเบียนและค่าตอบแทนจะถูกส่งกลับในการลงทะเบียน ดังนั้นเราจึงสามารถนำเอาคำแนะนำส่วนใหญ่ออกมาได้ส่วนมากจะเกี่ยวข้องกับการบันทึกการลงทะเบียนและการตั้งค่าสแต็กเฟรมใหม่ เราไม่ใช้สแต็คที่นี่และใช้รีจิสเตอร์เท่านั้นดังนั้นไม่จำเป็นต้องกังวลเกี่ยวกับรีจิสเตอร์อื่น ใบนี้ประกอบรหัส "golfed":int f(int n);
EDI
EAX
EAX
.globl f
f:
bsfl %edi, %eax
ret
หมายเหตุตามที่ @zwol ชี้ให้เห็นคุณยังสามารถใช้การรวบรวมที่ปรับให้เหมาะสมเพื่อให้ได้ผลลัพธ์ที่คล้ายกัน โดยเฉพาะอย่างยิ่ง-Os
จะสร้างคำแนะนำข้างต้นอย่างแน่นอน (พร้อมกับคำสั่งแอสเซมเบลอร์เพิ่มเติมบางรายการที่ไม่สร้างโค้ดออบเจ็กต์เพิ่มเติม)
ตอนนี้ประกอบกับgcc -c evenness.s -o evenness.o
ซึ่งสามารถเชื่อมโยงกับโปรแกรมควบคุมการทดสอบตามที่อธิบายไว้ข้างต้น
มีหลายวิธีในการกำหนดรหัสเครื่องที่สอดคล้องกับชุดประกอบนี้ สิ่งที่ฉันชอบคือการใช้disass
คำสั่งgdb disassembly:
$ gdb ./evenness
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
...
Reading symbols from ./evenness...(no debugging symbols found)...done.
(gdb) disass /r f
Dump of assembler code for function f:
0x00000000004005ae <+0>: 0f bc c7 bsf %edi,%eax
0x00000000004005b1 <+3>: c3 retq
0x00000000004005b2 <+4>: 66 2e 0f 1f 84 00 00 00 00 00 nopw %cs:0x0(%rax,%rax,1)
0x00000000004005bc <+14>: 0f 1f 40 00 nopl 0x0(%rax)
End of assembler dump.
(gdb)
ดังนั้นเราจะเห็นว่ารหัสเครื่องสำหรับbsf
การเรียนการสอนเป็น0f bc c7
และมีret
c3