Chức năng của các lệnh push / pop được sử dụng trên các thanh ghi trong hợp ngữ x86 là gì?


101

Khi đọc về trình hợp dịch, tôi thường bắt gặp mọi người viết rằng họ đẩy một thanh ghi nhất định của bộ xử lý và bật lại sau để khôi phục trạng thái trước đó của nó.

  • Làm thế nào bạn có thể đẩy một đăng ký? Nó được đẩy vào đâu? Tại sao điều này là cần thiết?
  • Điều này dẫn đến một chỉ dẫn xử lý đơn lẻ hay nó phức tạp hơn?

3
Cảnh báo: tất cả các câu trả lời hiện tại được đưa ra trong cú pháp hợp ngữ của Intel; push-pop trong cú pháp của AT & T ví dụ sử dụng một hậu khắc phục như b, w, l, hoặc qđể biểu thị kích thước của bộ nhớ bị thao túng. Ví dụ: pushl %eaxpopl %eax
Hawken

5
@hawken Trên hầu hết các trình lắp ráp có thể nuốt cú pháp AT&T (đặc biệt là gas), hậu tố kích thước có thể bị bỏ qua nếu kích thước toán hạng có thể được suy ra từ kích thước toán hạng. Đây là trường hợp cho các ví dụ bạn đã đưa ra, %eaxluôn có kích thước 32 bit.
Gunther Piez

Câu trả lời:


153

đẩy một giá trị (không nhất thiết phải được lưu trữ trong một thanh ghi) có nghĩa là ghi nó vào ngăn xếp.

popping có nghĩa là khôi phục bất cứ thứ gì ở trên cùng của ngăn xếp vào một thanh ghi. Đó là những hướng dẫn cơ bản:

push 0xdeadbeef      ; push a value to the stack
pop eax              ; eax is now 0xdeadbeef

; swap contents of registers
push eax
mov eax, ebx
pop ebx

5
Toán hạng rõ ràng cho push và pop r/m, không chỉ là đăng ký, vì vậy bạn có thể push dword [esi]. Hoặc thậm chí pop dword [esp]để tải và sau đó lưu trữ cùng một giá trị trở lại cùng một địa chỉ. ( github.com/HJLebbink/asm-dude/wiki/POP ). Tôi chỉ đề cập đến điều này bởi vì bạn nói "không nhất thiết phải đăng ký".
Peter Cordes

2
Bạn cũng có thể popvào một khu vực của bộ nhớ:pop [0xdeadbeef]
SS Anne

Xin chào, sự khác biệt giữa push / pop và pushq / popq là gì? Tôi đang sử dụng macos / intel
SteakOverflow

46

Đây là cách bạn đẩy một đăng ký. Tôi cho rằng chúng ta đang nói về x86.

push ebx
push eax

Nó được đẩy vào ngăn xếp. Giá trị của ESPthanh ghi được giảm xuống kích thước của giá trị được đẩy khi ngăn xếp tăng dần xuống trong hệ thống x86.

Nó là cần thiết để bảo tồn các giá trị. Cách sử dụng chung là

push eax           ;   preserve the value of eax
call some_method   ;   some method is called which will put return value in eax
mov  edx, eax      ;    move the return value to edx
pop  eax           ;    restore original eax

A pushlà một lệnh đơn trong x86, thực hiện hai việc bên trong.

  1. Giảm thanh ESPghi theo kích thước của giá trị được đẩy.
  2. Lưu trữ giá trị được đẩy tại địa chỉ hiện tại của ESPthanh ghi.

@vavan vừa gửi yêu cầu sửa lỗi
jgh fun-run

1
@vavan: Tôi đã chỉnh sửa.
Nate Eldredge

38

Nó được đẩy vào đâu?

esp - 4. Chính xác hơn:

  • esp bị trừ đi 4
  • giá trị được đẩy lên esp

pop đảo ngược điều này.

Hệ thống V ABI yêu cầu Linux rspchỉ đến một vị trí ngăn xếp hợp lý khi chương trình bắt đầu chạy: Trạng thái thanh ghi mặc định khi chương trình khởi chạy (asm, linux) là gì? đó là những gì bạn thường nên sử dụng.

Làm thế nào bạn có thể đẩy một đăng ký?

Ví dụ về GNU GAS tối thiểu:

.data
    /* .long takes 4 bytes each. */
    val1:
        /* Store bytes 0x 01 00 00 00 here. */
        .long 1
    val2:
        /* 0x 02 00 00 00 */
        .long 2
.text
    /* Make esp point to the address of val2.
     * Unusual, but totally possible. */
    mov $val2, %esp

    /* eax = 3 */
    mov $3, %ea 

    push %eax
    /*
    Outcome:
    - esp == val1
    - val1 == 3
    esp was changed to point to val1,
    and then val1 was modified.
    */

    pop %ebx
    /*
    Outcome:
    - esp == &val2
    - ebx == 3
    Inverses push: ebx gets the value of val1 (first)
    and then esp is increased back to point to val2.
    */

Trên GitHub với các xác nhận có thể chạy được .

Tại sao điều này là cần thiết?

Đúng là những hướng dẫn đó có thể dễ dàng thực hiện thông qua mov, addsub.

Lý do chúng tồn tại là do sự kết hợp các hướng dẫn đó quá thường xuyên, nên Intel đã quyết định cung cấp chúng cho chúng tôi.

Lý do tại sao những sự kết hợp đó rất thường xuyên, là chúng giúp dễ dàng lưu và khôi phục các giá trị của thanh ghi vào bộ nhớ tạm thời để chúng không bị ghi đè.

Để hiểu vấn đề, hãy thử biên dịch một số mã C bằng tay.

Một khó khăn lớn là quyết định nơi lưu trữ mỗi biến.

Lý tưởng nhất là tất cả các biến sẽ phù hợp với thanh ghi, đây là bộ nhớ nhanh nhất để truy cập (hiện tại nhanh hơn khoảng 100 lần so với RAM).

Nhưng tất nhiên, chúng ta có thể dễ dàng có nhiều biến hơn so với thanh ghi, đặc biệt cho các đối số của các hàm lồng nhau, vì vậy giải pháp duy nhất là ghi vào bộ nhớ.

Chúng ta có thể ghi vào bất kỳ địa chỉ bộ nhớ nào, nhưng vì các biến cục bộ và đối số của các lệnh gọi và trả về hàm phù hợp với một mẫu ngăn xếp đẹp mắt, ngăn chặn phân mảnh bộ nhớ , đó là cách tốt nhất để giải quyết nó. So sánh điều đó với sự điên rồ khi viết một trình phân bổ đống.

Sau đó, chúng tôi để các trình biên dịch tối ưu hóa việc phân bổ thanh ghi cho chúng tôi, vì đó là NP đã hoàn thành và một trong những phần khó nhất khi viết trình biên dịch. Vấn đề này được gọi là cấp phát thanh ghi , và nó là đẳng cấu để tô màu đồ thị .

Khi trình cấp phát của trình biên dịch buộc phải lưu trữ mọi thứ trong bộ nhớ thay vì chỉ đăng ký, điều đó được gọi là tràn .

Điều này dẫn đến một chỉ dẫn xử lý đơn lẻ hay nó phức tạp hơn?

Tất cả những gì chúng ta biết chắc chắn là Intel ghi lại một tài liệu pushvà một pophướng dẫn, vì vậy chúng là một hướng dẫn theo nghĩa đó.

Bên trong, nó có thể được mở rộng thành nhiều vi mã, một để sửa đổi espvà một để thực hiện IO bộ nhớ và mất nhiều chu kỳ.

Nhưng cũng có thể một đơn pushnhanh hơn một tổ hợp tương đương của các hướng dẫn khác, vì nó cụ thể hơn.

Điều này chủ yếu là chưa (der) được ghi lại:


4
Bạn không cần phải đoán về cách push/ popgiải mã thành uops. Nhờ các bộ đếm hiệu suất, có thể thử nghiệm thử nghiệm và Agner Fog đã thực hiện điều đó và xuất bản các bảng hướng dẫn . Pentium-M và các CPU mới hơn có một uop push/ popnhờ công cụ ngăn xếp (Xem pdf microarch của Agner). Điều này bao gồm các CPU AMD gần đây, nhờ vào thỏa thuận chia sẻ bằng sáng chế Intel / AMD.
Peter Cordes

@PeterCordes thật tuyệt vời! Vì vậy, các bộ đếm hiệu suất được ghi lại bởi Intel để đếm các hoạt động vi mô?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ngoài ra, các biến cục bộ tràn ra từ regs thường sẽ vẫn nóng trong bộ đệm L1 nếu bất kỳ biến nào trong số chúng thực sự đang được sử dụng. Nhưng việc đọc từ sổ đăng ký hoàn toàn miễn phí, không có độ trễ. Vì vậy, nó nhanh hơn vô hạn so với bộ nhớ cache L1, tùy thuộc vào cách bạn muốn xác định các thuật ngữ. Đối với các local chỉ đọc tràn vào ngăn xếp, chi phí chính chỉ là uops tải phụ (đôi khi là toán hạng bộ nhớ, đôi khi có các movtải riêng biệt ). Đối với các biến không phải const bị tràn, các chuyến đi vòng quanh cửa hàng có độ trễ tăng thêm (thêm ~ 5c so với chuyển tiếp trực tiếp và hướng dẫn tại cửa hàng không hề rẻ).
Peter Cordes

Vâng, có bộ đếm cho tổng số lần truy cập ở một vài giai đoạn đường ống khác nhau (sự cố / thực thi / ngừng hoạt động), vì vậy bạn có thể đếm tên miền hợp nhất hoặc miền không sử dụng. Hãy xem câu trả lời này chẳng hạn. Nếu tôi đang viết lại câu trả lời đó bây giờ, tôi sẽ sử dụng ocperf.pytập lệnh trình bao bọc để có được những tên tượng trưng dễ hiểu cho bộ đếm.
Peter Cordes

23

Thanh ghi đẩy và bật lên ở phía sau tương đương với điều này:

push reg   <= same as =>      sub  $8,%rsp        # subtract 8 from rsp
                              mov  reg,(%rsp)     # store, using rsp as the address

pop  reg    <= same as=>      mov  (%rsp),reg     # load, using rsp as the address
                              add  $8,%rsp        # add 8 to the rsp

Lưu ý đây là cú pháp x86-64 At & t.

Được sử dụng như một cặp, điều này cho phép bạn lưu một đăng ký trên ngăn xếp và khôi phục nó sau này. Có những công dụng khác nữa.


4
Có, những chuỗi đó mô phỏng chính xác push / pop. (ngoại trừ push / pop không ảnh hưởng đến cờ).
Peter Cordes

2
Tốt hơn bạn nên sử dụng lea rsp, [rsp±8]thay vì add/ subđể mô phỏng tốt hơn hiệu ứng của push/ poptrên cờ.
Ruslan

12

Hầu hết tất cả các CPU đều sử dụng ngăn xếp. Ngăn xếp chương trình là kỹ thuật LIFO với phần cứng được hỗ trợ quản lý.

Ngăn xếp là lượng bộ nhớ chương trình (RAM) thường được cấp phát ở trên cùng của đống bộ nhớ CPU và phát triển (theo lệnh PUSH, con trỏ ngăn xếp được giảm xuống) theo hướng ngược lại. Một thuật ngữ tiêu chuẩn để chèn vào ngăn xếp là PUSH và để loại bỏ khỏi ngăn xếp là POP .

Ngăn xếp được quản lý thông qua thanh ghi CPU dành cho ngăn xếp, còn được gọi là con trỏ ngăn xếp, vì vậy khi CPU thực hiện POP hoặc PUSH , con trỏ ngăn xếp sẽ tải / lưu trữ một thanh ghi hoặc hằng số vào bộ nhớ ngăn xếp và con trỏ ngăn xếp sẽ tự động giảm hoặc tăng lên theo số từ được đẩy hoặc được đưa vào (từ) ngăn xếp.

Thông qua hướng dẫn trình lắp ráp, chúng tôi có thể lưu trữ để xếp chồng:

  1. Thanh ghi CPU và các hằng số.
  2. Trả lại địa chỉ cho các hàm hoặc thủ tục
  3. Các hàm / thủ tục vào / ra các biến
  4. Hàm / thủ tục biến cục bộ.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.