bic : AC Interpreter และ API Explorerโครงการนี้ช่วยให้นักพัฒนาสำรวจและทดสอบ C-APIS โดยใช้ลูปพิมพ์การอ่านแบบอ่านหรือที่เรียกว่า REPL

การพึ่งพาเวลาการทำงานของ BIC มีดังนี้:
ในการสร้าง BIC คุณจะต้อง:
โปรดตรวจสอบให้แน่ใจว่าคุณติดตั้งสิ่งเหล่านี้ก่อนที่จะสร้าง BIC คำสั่งต่อไปนี้ควรติดตั้งสิ่งเหล่านี้ในระบบ Debian/Ubuntu:
apt-get ติดตั้ง build-essential libreadline-dev autoconf-archive libgmp-dev คาดว่า Flex Bison Automake M4 libtool pkg-config
นอกจากนี้คุณยังสามารถใช้คำสั่งต่อไปนี้เพื่อติดตั้งการพึ่งพาที่ต้องการผ่าน Homebrew บนระบบ MacOS
Brew ติดตั้ง Bison Flex GMP Readline AutoConf-Archive
คุณสามารถรวบรวมและติดตั้ง BIC ด้วยคำสั่งต่อไปนี้:
autoreconf -i ./configure-enable-debug ทำ ติดตั้ง
สำหรับการสร้างระบบ MacOS คุณต้องเปลี่ยนสายการกำหนดค่าเป็น:
yacc = "$ (brew -prefix bison)/bin/bison -y" ./configure -enable -debug
คุณสามารถใช้ Docker เพื่อสร้างและเรียกใช้ BIC ด้วยคำสั่งต่อไปนี้:
Docker build -t bic https://github.com/hexagonal-sun/bic.git#master
เมื่อสร้างภาพแล้วคุณสามารถเรียกใช้ BIC ด้วย:
Docker Run -i bic
หากคุณใช้ Arch Linux คุณสามารถติดตั้ง BIC จาก AUR:
Yay -s Bic
เมื่อเรียกใช้ BIC โดยไม่มีอาร์กิวเมนต์ผู้ใช้จะถูกนำเสนอด้วยพรอมต์ Repl:
BIC>
ที่นี่คุณสามารถพิมพ์คำสั่ง C และ #include ส่วนหัวของระบบต่างๆเพื่อให้การเข้าถึง APIs ที่แตกต่างกันในระบบ ข้อความสามารถป้อนโดยตรงไปยัง REPL; ไม่จำเป็นต้องกำหนดฟังก์ชั่นสำหรับพวกเขาที่จะได้รับการประเมิน สมมติว่าเราต้องการดำเนินการโปรแกรม C ต่อไปนี้:
#include <stdio.h>
int main ()
{
FILE * f = fopen ( "out.txt" , "w" );
fputs ( "Hello, world!n" , f );
return 0 ;
}เราสามารถทำสิ่งนี้บน REPL ด้วย BIC โดยใช้คำสั่งต่อไปนี้:
bic> #include <stdio.h>
bic> ไฟล์ *f;
f
bic> f = fopen ("test.txt", "w");
bic> fputs ("สวัสดีโลก! n", f);
1
BIC>
สิ่งนี้จะทำให้ BIC เรียกใช้ฟังก์ชั่น C-Library fopen() และ fputs() เพื่อสร้างไฟล์และเขียนสตริง Hello World ลงไป หากคุณออกจาก BIC ตอนนี้คุณควรเห็น test.txt ไฟล์ txt ในไดเรกทอรีการทำงานปัจจุบันด้วยสตริง Hello, Worldn ที่มีอยู่ภายใน
ขอให้สังเกตว่าหลังจากประเมินการแสดงออกของ BIC จะพิมพ์ผลลัพธ์ของการประเมินผล สิ่งนี้มีประโยชน์สำหรับการทดสอบนิพจน์อย่างง่าย:
BIC> 2 * 8 + FILENO (F); 19
คุณสามารถใช้ BIC เพื่อรับข้อมูลเกี่ยวกับตัวแปรหรือประเภทใด ๆ ที่ได้รับการประกาศโดยคำนำหน้าชื่อของมันด้วย ? - ไวยากรณ์พิเศษนี้ใช้งานได้ใน REPL เท่านั้น แต่จะช่วยให้คุณได้รับคุณสมบัติต่าง ๆ เกี่ยวกับประเภทและตัวแปร ตัวอย่างเช่น:
bic> #include <stdio.h> bic>? stdout stdout เป็นตัวชี้ไปยัง struct _io_file ค่าของ stdout คือ 0x7ff1325bc5c0 sizeof (stdout) = 8 ไบต์ stdout ได้รับการประกาศที่: /usr/include/stdio.h:138
เมื่อการเปลี่ยนเริ่มต้น BIC จะเห็นว่า ~/.bic มีอยู่หรือไม่ ถ้ามันได้รับการประเมินโดยอัตโนมัติและการใช้สิ่งแวดล้อมที่เกิดขึ้นจะถูกใช้โดย REPL สิ่งนี้มีประโยชน์สำหรับการกำหนดฟังก์ชั่นหรือตัวแปรที่ใช้กันทั่วไป ตัวอย่างเช่นพูดว่าไฟล์ ~/.bic ของเรามี:
#include <stdio.h>
int increment ( int a )
{
return a + 1 ;
}
puts ( "Good morning, Dave." );เมื่อเราเปิดตัว REPL เราได้รับ:
$ bic สวัสดีตอนเช้าเดฟ BIC> เพิ่มขึ้น (2); 3
หากคุณผ่านไฟล์ต้นฉบับ BIC พร้อมกับ -s เป็นอาร์กิวเมนต์บรรทัดคำสั่งมันจะประเมินโดยเรียกฟังก์ชัน main() ตัวอย่างเช่นสมมติว่าเรามี test.c ไฟล์ที่มีสิ่งต่อไปนี้:
#include <stdio.h>
int factorial ( int n )
{
if (! n )
{
return 1 ;
}
return n * factorial ( n - 1 );
}
int main ()
{
printf ( "Factorial of 4 is: %dn" , factorial ( 4 ));
return 0 ;
} จากนั้นเราสามารถเรียกใช้ BIC ด้วย -s test.c เพื่อประเมิน:
$ bic -s test.c แฟคทอเรียล 4 คือ 24
หากคุณต้องการส่งอาร์กิวเมนต์ไปยังไฟล์ C ให้ต่อท้ายบรรทัดคำสั่งของ BIC เมื่อ BIC ได้ประมวลผลอาร์กิวเมนต์ -s อาร์กิวเมนต์อื่น ๆ ทั้งหมดจะถือว่าเป็นพารามิเตอร์ที่จะส่งผ่านไปยังโปรแกรม พารามิเตอร์เหล่านี้ถูกสร้างขึ้นเป็นตัวแปร argc และ argv และส่งผ่านไปยัง main() ค่าของ argv[0] คือชื่อของไฟล์ C ที่ BIC กำลังดำเนินการอยู่ พิจารณาโปรแกรม C ต่อไปนี้:
#include <stdio.h>
int main ( int argc , char * argv [])
{
for ( int i = 0 ; i < argc ; i ++ )
printf ( "argv[%d] = %sn" , i , argv [ i ]);
return 0 ;
}ถ้าเราไม่ผ่านข้อโต้แย้งใด ๆ :
$ bic -s test.c argv [0] = test.c
ในขณะที่ถ้าเราเรียกใช้ BIC ด้วยข้อโต้แย้งเพิ่มเติมพวกเขาจะถูกส่งผ่านไปยังโปรแกรม:
$ bic -s test.c -a foo -s bar abc argv [0] = test.c argv [1] = -a argv [2] = foo argv [3] = -s argv [4] = bar argv [5] = a argv [6] = b argv [7] = c
นอกจากนี้คุณยังสามารถใช้นิพจน์พิเศษได้: <REPL>; ในซอร์สโค้ดของคุณเพื่อให้ BIC ส่งคุณไปยังการแก้ไขที่จุดใดจุดหนึ่งในการประเมินไฟล์:

คุณสามารถใช้ BIC เพื่อสำรวจ API ของห้องสมุดอื่น ๆ นอกเหนือจาก LIBC สมมติว่าเราต้องการสำรวจไลบรารี Capstone เราผ่านตัวเลือก -l เพื่อให้โหลด BIC นั้นเมื่อเริ่มต้น ตัวอย่างเช่น:

ขอให้สังเกตว่าเมื่อ BIC พิมพ์ประเภทข้อมูลแบบผสม ( struct หรือ union ) จะแสดงชื่อสมาชิกทั้งหมดและค่าที่สอดคล้องกัน
หัวใจสำคัญของการใช้งานของ BIC คือวัตถุ tree เหล่านี้เป็นวัตถุทั่วไปที่สามารถใช้เพื่อเป็นตัวแทนของโปรแกรมทั้งหมดรวมถึงสถานะผู้ประเมินปัจจุบัน มันถูกนำไปใช้ใน tree.h และ tree.c แต่ละประเภทต้นไม้ถูกกำหนดใน c.lang ไฟล์ c.lang เป็นข้อมูลจำเพาะเหมือน LISP ของ:
T_ADDAdditiontADDLHS และ RHSรหัสเพื่อสร้างวัตถุที่มีชุดแอตทริบิวต์ด้านบนจะเป็น:
( deftype T_ADD " Addition " " tADD "
( " LHS " " RHS " ))เมื่อกำหนดแล้วเราสามารถใช้วัตถุนี้ในรหัส C ของเราด้วยวิธีต่อไปนี้:
tree make_increment ( tree number )
{
tree add = tree_make ( T_ADD );
tADD_LHS ( add ) = number ;
tADD_RHS ( add ) = tree_make_const_int ( 1 );
return add ;
} ขอให้สังเกตว่าชุดของมาโคร accessor, tADD_LHS() และ tADD_RHS() ได้ถูกสร้างขึ้นเพื่อให้เราเข้าถึงสล็อตคุณสมบัติที่แตกต่างกัน เมื่อ --enable-debug ถูกตั้งค่าในระหว่างการรวบรวมแต่ละมาโครเหล่านี้จะขยายไปยังการตรวจสอบเพื่อให้แน่ใจว่าเมื่อตั้งค่าคุณสมบัติ tADD_LHS ของวัตถุที่วัตถุนั้นเป็นอินสแตนซ์ของ T_ADD
ไฟล์ c.lang ถูกอ่านโดยคอมไพเลอร์แหล่งข้อมูลจำนวนมากที่สร้างตัวอย่างโค้ด ยูทิลิตี้เหล่านี้รวมถึง:
gentype : สร้างรายการประเภทวัตถุต้นไม้gentree : สร้างโครงสร้างที่มีข้อมูลคุณสมบัติทั้งหมดสำหรับวัตถุต้นไม้genctypes : สร้างรายการของวัตถุต้นไม้ชนิด C - สิ่งเหล่านี้แสดงถึงประเภทข้อมูลพื้นฐานใน C.genaccess : สร้างมาโคร accessor สำหรับคุณสมบัติวัตถุต้นไม้gengc : สร้างฟังก์ชั่นเครื่องหมายสำหรับวัตถุต้นไม้แต่ละชิ้นซึ่งจะช่วยให้ตัวเก็บขยะในการสำรวจต้นไม้วัตถุgendump : สร้างรหัสเพื่อทิ้งวัตถุต้นไม้ซ้ำgendot : สร้างไฟล์ DOT สำหรับลำดับชั้นของ tree ที่กำหนดเพื่อให้สามารถมองเห็นได้ เอาต์พุตของ Lexer & Parser เป็นลำดับชั้นของวัตถุ tree ซึ่งจะถูกส่งผ่านไปยังผู้ประเมิน ( evaluator.c ) จากนั้นผู้ประเมินจะประเมินองค์ประกอบของต้นไม้แต่ละรายการซ้ำแล้วซ้ำอีกโดยอัปเดตสถานะผู้ประเมินภายในซึ่งจะดำเนินการโปรแกรม
การโทรไปยังฟังก์ชั่นภายนอกไปยังผู้ประเมินจะได้รับการจัดการในวิธีที่ขึ้นกับแพลตฟอร์ม ปัจจุบัน X86_64 และ AARCH64 เป็นแพลตฟอร์มที่รองรับเพียงอย่างเดียวและรหัสเพื่อจัดการสิ่งนี้อยู่ในโฟลเดอร์ x86_64 และ aarch64 ตามลำดับ สิ่งนี้ใช้งานได้โดยการใช้วัตถุ tree รีฟังก์ชั่น (แสดงโดย T_FN_CALL ) จากผู้ประเมินพร้อมกับอาร์กิวเมนต์ทั้งหมดที่ประเมินและจัดการกับพวกเขาลงในรายการที่เชื่อมโยงอย่างง่าย สิ่งนี้จะถูกสำรวจในแอสเซมบลีเพื่อย้ายค่าไปยังการลงทะเบียนที่ถูกต้องตาม X86_64 หรือ AARCH64 การเรียกร้องการโทรจากนั้นแยกไปยังที่อยู่ฟังก์ชั่น
Parser และ Lexer ถูกนำไปใช้ใน parser.m4 และ lex.m4 ตามลำดับ หลังจากผ่าน M4 เอาท์พุทคือตัวแยกวิเคราะห์สองตัวและสองตัวยืดหยุ่น
เหตุผลสำหรับการแยกวิเคราะห์สองตัวคือไวยากรณ์สำหรับ C Repl นั้นแตกต่างจากไฟล์ C มาก ตัวอย่างเช่นเราต้องการให้ผู้ใช้สามารถพิมพ์คำสั่งที่จะประเมินได้ใน REPL โดยไม่จำเป็นต้องห่อไว้ในฟังก์ชั่น น่าเสียดายที่การเขียนคำสั่งที่อยู่นอกร่างกายฟังก์ชั่นไม่ถูกต้อง C. เช่นนี้เราไม่ต้องการให้ผู้ใช้สามารถเขียนคำสั่งเปลือยในไฟล์ C ได้ เพื่อให้บรรลุเป้าหมายนี้เรามีกฎไวยากรณ์สองชุดที่แตกต่างกันซึ่งสร้างตัวแยกวิเคราะห์สองตัว กฎไวยากรณ์ส่วนใหญ่ทับซ้อนกันดังนั้นเราจึงใช้ไฟล์ M4 เดียวเพื่อดูแลความแตกต่าง