2009-10-30 49 views
16

Tôi thích viết một bộ x86 cho một dự án sở thích.Cách tốt nhất để viết về cách viết một bộ x86 đơn giản là gì?

Lúc đầu nó có vẻ khá thẳng về phía trước với tôi nhưng tôi càng đọc nó, càng có nhiều câu hỏi chưa được trả lời tôi thấy mình có. Tôi không hoàn toàn thiếu kinh nghiệm: Tôi đã sử dụng MIPs một số tiền hợp lý và tôi đã viết một trình biên dịch đồ chơi cho một tập hợp con của C trong trường học.

Mục tiêu của tôi là viết một trình xử lý x86 đơn giản nhưng có chức năng. Tôi không muốn tạo ra một bộ lắp ráp thương mại, nhưng chỉ đơn giản là một dự án sở thích để tăng cường kiến ​​thức của tôi trong một số lĩnh vực nhất định. Vì vậy, tôi không nhớ nếu tôi không thực hiện mọi tính năng và hoạt động có sẵn.

Tôi có nhiều câu hỏi như: Tôi có nên sử dụng phương pháp một hoặc hai lần không? Tôi có nên sử dụng phân tích cú pháp quảng cáo hay xác định ngữ pháp chính thức và sử dụng trình tạo phân tích cú pháp cho hướng dẫn của mình không? Ở giai đoạn nào, và làm cách nào để giải quyết các địa chỉ của các biểu tượng của tôi?

Với yêu cầu của tôi, bất cứ ai có thể đề xuất một số hướng dẫn chung cho các phương pháp tôi nên sử dụng trong bộ sưu tập dự án thú cưng của tôi?

+1

này nhắc nhở tôi về những thử nghiệm lập trình từ những năm 1980, cho một đĩa mềm với chỉ command.com và debug.com vào nó, những gì loại của một môi trường phát triển bạn sẽ tạo cho bản thân. Tôi biết làm thế nào những người Forth trả lời. – zumalifeguard

Trả lời

1

Bạn sẽ cần phải viết một lexer và phân tích cú pháp để đọc trong mã nguồn và xuất ra cây cú pháp trừu tượng (AST). AST sau đó có thể được đi qua để tạo ra đầu ra mã byte.

Tôi khuyên bạn nên nghiên cứu sách về viết trình biên dịch. Đây thường là một cấp độ đại học, vì vậy sẽ có rất nhiều sách. Xin lỗi, tôi không thể giới thiệu một cách cụ thể.

Bạn cũng có thể đọc trên công cụ ANTLR. Nó có thể thực hiện các quy tắc ngữ pháp và mã đầu ra bằng nhiều ngôn ngữ khác nhau để làm việc lexer/parser làm việc cho bạn.

Trên thẻ một hoặc hai lần: bạn sẽ cần trình biên dịch hai bước để giải quyết các tham chiếu chuyển tiếp. Nếu đó không phải là quan trọng, thì một lượt sẽ làm. Tôi khuyên bạn nên giữ nó đơn giản, vì đây là trình biên dịch đầu tiên của bạn.

+0

Nếu bạn đọc câu hỏi của tôi, đó không phải là trình biên dịch đầu tiên của tôi. Tôi đã sử dụng Lex/Yacc trước đây, và tôi có một sự hiểu biết chung về ANTLR. Có vẻ như nhiều tài nguyên trực tuyến và thậm chí trên SO đề xuất sử dụng phân tích cú pháp Ad-hoc khi viết một trình biên dịch. Bạn đồng ý hay không đồng ý? – mmcdole

+0

Ok, tôi nghĩ rằng tôi đã không hoàn toàn hiểu những gì bạn đang yêu cầu. Mặc dù, nếu bạn đã viết một lexer/parser cho một ngôn ngữ giống như C, thì trình biên dịch x86 sẽ là một cinch. Thoạt nhìn, tôi muốn nói rằng các nút AST sẽ chứa siêu dữ liệu cho mỗi nguyên thủy, chẳng hạn như bù đắp byte, tham chiếu biểu tượng, v.v. một nhà điều hành chi nhánh sẽ tham chiếu một nút nhãn, trong đó có bù đắp của nó. – spoulson

6

Bạn có thể tìm thấy dragon book để hữu ích.

Tiêu đề thực tế là Compilers: Principles, Techniques, and Tools (amazon.com).

Xem Intel Architectures Software Developer's Manuals để biết tài liệu đầy đủ về các tập lệnh IA-32 và IA-64.

AMD's architecture technical documents cũng có sẵn trên trang web của nó.

Linkers and Loaders (amazon.com) là một giới thiệu tốt về các định dạng đối tượng và các vấn đề liên kết. (unedited original manuscript cũng có sẵn trực tuyến.)

+6

Trong khi tôi tôn trọng cuốn sách Rồng như là văn bản dứt khoát trên các trình biên dịch, tôi không nghĩ rằng nó được sử dụng nhiều khi viết một trình biên dịch. Các vấn đề phân tích liên quan đến các trình biên dịch có nhiều khác biệt so với các trình biên dịch thực, và việc tạo mã thực chất là một câu lệnh ngôn ngữ assembly — no-op bản đồ hướng dẫn một máy. –

+0

Thậm chí không phải trình biên dịch. Đó là nhiều trình phân tích cú pháp hơn. Các phần khác nhận được một chương tối đa. –

1

Cho rằng đây là một dự án sở thích, rất nhiều câu hỏi của bạn thực sự đi đến 'khía cạnh nào của vấn đề bạn quan tâm nhất khi xem và tìm hiểu?' Nếu bạn quan tâm đến việc xem các công cụ phân tích cú pháp ánh xạ tới vấn đề của các trình assembler như thế nào (đặc biệt là khi nó xử lý macro và tương tự), bạn nên sử dụng chúng. Mặt khác, nếu bạn không quá quan tâm đến những câu hỏi đó và chỉ muốn đưa vào các câu hỏi về đóng gói và bố cục lệnh và có nội dung để có một trình biên dịch tối thiểu không có macro, thì nhanh chóng và dơ bẩn để phân tích cú pháp có lẽ là con đường để đi.

Để vượt qua một lần so với số nhân - bạn có muốn chơi với chế tạo một bộ lắp ráp rất nhanh với bộ nhớ được thu nhỏ không? Nếu vậy, câu hỏi này trở nên có liên quan.Nếu không, chỉ cần slurp toàn bộ chương trình vào bộ nhớ, đối phó với nó ở đó, tạo ra một hình ảnh đối tượng trong bộ nhớ, và sau đó viết ra. Không thực sự cần phải lo lắng về 'vượt qua' như vậy. Trong mô hình này, bạn có thể dễ dàng chơi xung quanh hơn bằng cách làm những thứ theo các thứ tự khác nhau để xem sự cân bằng là gì, đó là phần lớn của một dự án sở thích.

2

Để trả lời một trong các câu hỏi của bạn, một lần không thể thực hiện được, trừ khi bạn phát ra mã sau khi vượt qua.

Hãy tưởng tượng này:

JMP some_label 
    .. code here 
some_label: 

những gì bạn phát ra như khoảng cách giá trị cho các hướng dẫn JMP? Bạn chỉ ra hướng dẫn JMP nào, lệnh nào yêu cầu giá trị đóng hoặc nhãn quá xa?

Vì vậy, hai thẻ phải ở mức tối thiểu.

+0

Một đường chuyền là tốt. Xem câu trả lời của tôi. –

1

Tôi thường mơ tưởng về việc cố gắng xây dựng một ngôn ngữ máy tính cấp cao khác. Các đối tượng sẽ được cố gắng để đẩy phong bì của sự phát triển nhanh chóng, và hiệu suất của kết quả. Tôi sẽ cố gắng xây dựng các thư viện của các hoạt động tối thiểu, được tối ưu hóa khá cao và sau đó cố gắng phát triển các quy tắc ngôn ngữ theo cách mà bất kỳ câu lệnh hoặc biểu thức nào thể hiện được bằng ngôn ngữ sẽ dẫn đến mã tối ưu .. trừ khi những gì được thể hiện là chỉ vốn kém tối ưu.

Nó sẽ biên dịch thành mã byte, mã sẽ được phân phối và sau đó đến mã máy khi được cài đặt hoặc khi môi trường bộ xử lý thay đổi. Vì vậy, khi một tập tin thực thi được tải, sẽ có một bộ tải sẽ kiểm tra bộ xử lý và một vài byte dữ liệu điều khiển trong đối tượng, và nếu hai phần khớp nhau, thì phần thực thi của đối tượng có thể được tải ngay lập tức, nhưng nếu không , sau đó mã byte cho đối tượng đó sẽ phải được biên dịch lại và phần thực thi được cập nhật. (Vì vậy, nó không phải là chỉ trong thời gian biên dịch - đó là trên chương trình cài đặt hoặc trên CPU thay đổi biên dịch.) Phần tải sẽ rất ngắn và ngọt ngào, nó sẽ được trong '386 mã vì vậy nó sẽ không cần phải được biên dịch. Nó sẽ chỉ tải trình biên dịch mã byte nếu cần, và nếu như vậy, nó sẽ tải một đối tượng trình biên dịch nhỏ và chặt, và tối ưu hóa cho kiến ​​trúc đã phát hiện. Lý tưởng nhất, bộ nạp và trình biên dịch sẽ ở lại thường trú, một khi được nạp, và sẽ chỉ có một ví dụ của cả hai.

Dù sao, tôi muốn trả lời ý tưởng rằng bạn phải có ít nhất hai lần chuyền - tôi không nghĩ rằng tôi hoàn toàn đồng ý. Có, tôi sẽ sử dụng một lần thứ hai thông qua mã được biên dịch, nhưng không phải thông qua mã nguồn.

Điều bạn làm là khi bạn bắt gặp biểu tượng, kiểm tra bảng băm biểu tượng của bạn và nếu không có mục nhập, hãy tạo một bảng và lưu trữ điểm đánh dấu 'chuyển tiếp' trong mã được biên dịch của bạn bằng con trỏ vào bảng mục nhập. Khi bạn đi qua các định nghĩa cho nhãn và biểu tượng, cập nhật (hoặc đưa dữ liệu mới vào) bảng biểu tượng của bạn.

Đối tượng được biên dịch riêng lẻ không bao giờ lớn đến mức chúng chiếm nhiều bộ nhớ, vì vậy, chắc chắn tất cả mã được biên dịch sẽ được giữ trong bộ nhớ cho đến khi toàn bộ nội dung sẵn sàng được viết ra. Cách bạn giữ chân bộ nhớ nhỏ của bạn chỉ đơn giản là bằng cách chỉ đối phó với một đối tượng tại một thời điểm, và bằng cách không bao giờ giữ nhiều hơn một bộ đệm nhỏ đầy đủ mã nguồn trong bộ nhớ tại một thời điểm. Có thể là 64k hay 128k hay gì đó. (Một cái gì đó đủ lớn mà các chi phí liên quan trong việc thực hiện cuộc gọi để tải bộ đệm từ đĩa là nhỏ so với thời gian cần để đọc dữ liệu từ đĩa, để streaming được tối ưu hóa.)

Vì vậy, một vượt qua thông qua luồng nguồn cho một đối tượng, sau đó bạn ghép các mảnh của bạn lại với nhau, thu thập thông tin tham chiếu cần thiết từ bảng băm khi bạn đi và nếu dữ liệu không có - đó là lỗi biên dịch. Đó là quá trình tôi sẽ bị cám dỗ để thử.

0

Tôi đã viết một vài trình phân tích cú pháp. Tôi đã viết vài trình phân tích cú pháp làm bằng tay và tôi đã thử loại trình phân tích cú pháp yacc quá ....

Trình phân tích cú pháp được thực hiện cung cấp sự linh hoạt hơn. Yacc cung cấp một khung công tác mà người ta phải thích ứng hoặc thất bại. Trình phân tích cú pháp của Yahoo cung cấp trình phân tích cú pháp nhanh theo mặc định nhưng sau khi thay đổi/giảm và giảm/giảm có thể đòi hỏi một nỗ lực nếu bạn không quen với một phương tiện đó và môi trường phân tích cú pháp của bạn không tốt nhất. Về lợi thế của Yacc. Nó cung cấp cho bạn một hệ thống nếu bạn cần. Trình phân tích cú pháp làm bằng tay cung cấp cho bạn sự tự do nhưng bạn có thể ghi lại nó không? Ngôn ngữ lắp ráp có vẻ đủ đơn giản để được xử lý bởi yacc hoặc các trình phân tích cú pháp tương tự.

Trình phân tích cú pháp thủ công của tôi sẽ chứa trình mã thông báo/lexer và tôi sẽ đi qua một loạt các thẻ với vòng lặp và thực hiện một số loại xử lý sự kiện bằng cách đặt ifs hoặc câu lệnh case trong vòng lặp và kiểm tra mã thông báo hiện tại hoặc tiếp theo Trước. Có thể tôi sẽ sử dụng một trình phân tích cú pháp riêng cho các biểu thức ... Tôi sẽ đặt mã dịch thành mảng các chuỗi và "ghi chú" các phần không được tính toán của mã đã dịch để chương trình có thể đến với chúng sau và điền vào các khoảng trống .. Có thể có khoảng trống và không phải mọi thứ đều được biết trước khi phân tích cú pháp mã. Ví dụ. vị trí nhảy.

Mặt khác, bất cứ cách nào bạn làm trình phân tích cú pháp của mình lần đầu tiên và bạn có thời gian, bạn có thể chuyển đổi trình phân tích cú pháp của bạn từ loại này sang loại khác. Tùy thuộc vào bạn là ai, bạn thậm chí có thể thích điều đó.

Có các trình phân tích cú pháp khác so với Yacc và chúng hứa hẹn linh hoạt hơn và ít lỗi hơn nhưng điều đó không có nghĩa là bạn không gặp lỗi, chúng sẽ không hiển thị và có thể không quá nhanh. Nếu điều đó quan trọng.

Nhân tiện, nếu các mã thông báo được lưu trữ, thậm chí có thể suy nghĩ về một trình phân tích cú pháp yacc và làm bằng tay.

1

Hãy bảng NASM, và cố gắng thực hiện các hướng dẫn cơ bản hơn, sử dụng các bảng để giải mã

4

Trong khi nhiều người đề nghị phân tích cú pháp ad-hoc, tôi nghĩ rằng những ngày này ta nên sử dụng một máy phát điện phân tích cú pháp vì nó thực sự đơn giản hoá vấn đề xây dựng tất cả cú pháp phức tạp cần cho một trình tạo hiện đại thú vị. Xem ví dụ/câu trả lời BNF của tôi về số StackOverflow: Z80 ASM BNF.

"Một lần" so với "Hai lần" đề cập đến việc bạn có đọc chính mã nguồn hai lần hay không. Bạn luôn có thể thực hiện một trình biên dịch một lần. Dưới đây là hai cách:

1) Tạo kết quả nhị phân khi đang bay (nghĩ về các cặp trong tóm tắt có xu hướng có địa chỉ tăng đơn điệu) và phát ra các bản vá lỗi dưới dạng bản sửa lỗi khi bạn tìm thấy thông tin cho phép bạn giải quyết các tham chiếu chuyển tiếp (Hãy xem chúng như những cặp mà các địa chỉ được sử dụng để ghi đè lên các vị trí được phát ra trước đó). Đối với JMP, hãy cam kết loại/kích thước của mã hóa JMP khi bạn gặp phải. Mặc định có thể là ngắn hoặc dài tùy thuộc vào khẩu vị hoặc thậm chí tùy chọn lắp ráp. Một chút nhỏ cú pháp được trình mã hóa nhập vào nói rằng "sử dụng loại khác" hoặc "Tôi nhấn mạnh vào loại này" (ví dụ: "JMP long target") để xử lý những trường hợp lựa chọn mặc định của trình biên dịch sai. (Đây là lắp ráp, OK của nó để có quy tắc funky).

2) Trên (đầu tiên) vượt qua, tạo dữ liệu cho bộ đệm trong bộ nhớ. JMP mặc định (và các hướng dẫn phụ thuộc vào khoảng thời gian khác) để bù ngắn. Ghi lại các vị trí của tất cả các JMP (các hướng dẫn phụ thuộc khoảng, vv). Vào cuối của đèo này, quay trở lại các JMP và sửa lại những cái "quá ngắn" để dài hơn; trộn mã và điều chỉnh các JMP khác.Một kế hoạch thông minh để thực hiện điều này và đạt được các bộ JMP ngắn gần như tối ưu là tài liệu trong giấy 1978 này: Assembling code for machines with span-dependent instructions/Szymanski

+0

Vâng, nếu nó chỉ là một dự án đồ chơi, nó có thể không cần phải hỗ trợ tất cả những thứ mà một người lắp ráp hiện đại nên. Hơn nữa, chúng tôi không biết (vì anh ấy không nói) nếu một trong những lĩnh vực mà OP có thể muốn cải thiện khi phân tích cú pháp. –

+0

Vấn đề là x86 assemblers có xu hướng có cú pháp lộn xộn cho các toán hạng lệnh. Đặc biệt đối với một dự án sở thích, một trình tạo trình phân tích cú pháp có ý nghĩa; không có nhiều điều để tìm hiểu về mã lắp ráp. –

Các vấn đề liên quan