Я пытаюсь установить HTTP-соединение из сборки с помощью Windows API. Код следующий:
%include "const.asm"
%define u(x) __utf16__(x)
%define w(x) __utf32__(x)
; Externs
extern InternetOpenA
extern GetLastError
section .data
curProc db 0
hInternet dq 0
lpszAgent db "Mozilla/5.0", 0
section .text
global Start
Start:
sub rsp, 8
sub rsp, 32 + 1 * 8
lea rcx, [REL lpszAgent] ; lpszAgentW (WIDE STRING)
mov rdx, 4 ; dwAccessType (DWORD)
mov r8, NULL ; lpszProxy (DWORD)
mov r9, NULL ; pszProxyBypassW (DWORD)
mov dword [rsp + 4 * 8], NULL ; dwFlags (DWORD)
call InternetOpenA
mov [REL hInternet], rax
add rsp, 32 + 1 * 8 ; hInternet parameter
cmp rax, NULL
je error_handler
; Stop
jmp exit
error_handler:
sub rsp, 32
call GetLastError ; Load more error info
add rsp, 32 + 8
ret
exit: ; Graceful shutdowns
xor rax, rax
add rsp, 8
ret
Соответствующий Makefile:
INCLUDES := kernel32.lib user32.lib wininet.lib
.PHONY: all compile link clean
all: build
compile:
@echo "Compiling assembly"
nasm -f win64 main.asm -o main.o
link: compile
@echo "Linking assembly"
polink /ENTRY:Start /SUBSYSTEM:CONSOLE /LIBPATH:C:\lib64 $(INCLUDES) main.o
build: link
clean:
@echo "Cleaning up"
del main.o
Когда я запускаю его, возвращается код ошибки -1073741819
или 0xC0000005
, как указал @selbie, или также известный как нарушение прав доступа. Я получил segfault как для wininet
, так и для winhttp
при создании дескриптора. Я прилагаю короткий код C, показывающий, что именно я хотел бы сделать:
(Сборка не должна иметь отпечатков. Она находится только в исходном коде C для целей отладки)
#include "windows.h"
#include "wininet.h"
#include <stdio.h>
int main() {
const char *agent = "Mozilla/5.0";
HINTERNET handler = InternetOpenA(agent, INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, 0, 0, 0);
if (handler) {
return 0;
}
printf("%d", GetLastError());
return 1;
}
Для ясности компилирую для 64-битной Windows. Я также статически привязываюсь, как показано на Makefile
.
Ваше здоровье
Обновлено: Код для некоторой отладки:
[BITS 64]
%include "const.asm"
%define u(x) __utf16__(x)
%define w(x) __utf32__(x)
; Externs
extern InternetOpenA
extern GetLastError
extern GetStdHandle
extern WriteFile
section .data
curProc db 0
hInternet dq 0
lpszAgent db "Mozilla/5.0", 0
NtlpBuffer: db 'Hello, World!', 0
NtnNBytesToWrite: dq 0eh
section .text
global Start
Start:
sub rsp, 8
mov rax, rsp
mov rbx, 8
xor rdx, rdx
div rbx
cmp rdx, 0
je main
sub rsp, 32 + 1 * 8
and rsp, -16
lea rcx, [REL lpszAgent] ; lpszAgentW (WIDE STRING)
mov rdx, 4 ; dwAccessType (DWORD)
mov r8, NULL ; lpszProxy (DWORD)
mov r9, NULL ; pszProxyBypassW (DWORD)
mov dword [rsp + 4 * 8], NULL ; dwFlags (DWORD)
call InternetOpenA
mov [REL hInternet], rax
add rsp, 32 + 1 * 8 ; hInternet parameter
cmp rax, NULL
je error_handler
; Stop
jmp exit
error_handler:
sub rsp, 32
call GetLastError ; Load more error info
add rsp, 32 + 8
ret
exit: ; Graceful shutdowns
xor rax, rax
add rsp, 8
ret
main:
sub rsp, 32
mov ecx, -11
call GetStdHandle
mov rcx, rax
lea rdx, [rel NtlpBuffer]
mov r8, [REL NtnNBytesToWrite]
mov r9, NULL
mov qword [rsp + 32], 00h
call WriteFile
add rsp, 32
ExitProgram:
xor eax, eax
add rsp, 8
ret
add rsp, 32 + 1 * 8
— вы знаете, что функции stdcall, экспортируемые Windows API, восстанавливают указатель стека от имени вызывающего объекта. Вы уверены, что ваш звонок для восстановления RSP необходим?
@selbie RE первый комментарий, спасибо за разъяснения, отредактировано. РЭ 2: Я не знаю об этом факте. Я использовал add rsp, 32 + n * 8
в предыдущих проектах, и все работало нормально. При этом ошибка возникает именно тогда, когда я call InternetOpenA
. Также спасибо за быстрый ответ.
Это 32-битный или 64-битный код?
@селби 64-бит. Что касается аргумента о восстановлении указателя стека: Learn.microsoft.com/en-us/cpp/build/…. И пролог, и эпилог вручную изменяют указатель стека.
@selbie: Windows x64 — это caller-pops, поэтому вы можете выделить теневое пространство (плюс пространство для передачи аргументов, если необходимо) один раз для функции, которая выполняет несколько вызовов, поэтому RSP никогда не выходит за пределы пролога/эпилога (кроме самого вызова) , что упрощает очистку стека метаданных.
Если Start
вызывается как обычная функция, ваш пролог смещает стек - общее изменение в RSP от двух инструкций sub
будет четным кратным 8. Но поскольку call
отправил адрес возврата, вам нужно нечетное кратное 8, чтобы получить из RSP % 16 == 8
при входе в RSP % 16 == 0
перед звонком. Код в библиотечной функции, которая использует movaps
в памяти стека, выдаст ошибку, если вы сделаете это неправильно, как в Linux, который имеет такое же требование к выравниванию (glibc scanf Ошибки сегментации при вызове из функции, которая не выравнивает RSP)
@Peter Cordes Спасибо за быстрый ответ! Я прочитал ваш ответ и снова попытался sub rsp, 8
получить нечетное число, кратное 8. Это все равно привело к сегфолту. Возможно, я неправильно истолковываю ваше решение по другому вопросу.
Хорошо, возможно, это не ваша ошибка. Используйте отладчик, чтобы выяснить, какая инструкция дает сбой.
Изменения указателя стека в вашей функции кажутся слишком сложными. Не нужны разные настройки в разных ветках, просто выделите достаточно в прологе и уберите в обоих эпилогах. (add rsp, 32 + 8*1
)
@Питер Кордес, ты уверен в этом? Без него я не могу вызвать, например. печать. Я проверил это, удалив эпилог после печати, но это дает мне ошибку. Когда я возвращаю эпилог, он работает.
@Peter Cordes Я добавил в вопрос немного отладочного кода. Этот код выполняется без проблем, печатает Hello World!
и возвращает ноль. Это также проверяет rsp
, глядя на остаток. Я изо всех сил пытаюсь настроить отладчик: gdb
, похоже, не распознает заголовки nasm
и polink/link
отладки.
В GDB используйте layout asm
для отладки на основе дизассемблирования машинного кода. Вам не нужны метаданные отладки. (А затем layout next
чтобы увидеть разделенное представление с дизассемблированием и регистрами. Советы по ассемблеру GDB смотрите внизу stackoverflow.com/tags/x86/info)
После нескольких часов попыток и неудач мой коллега-программист @sbdswr из Discord помог мне отладить его — как ни странно, без всякого отладчика. Проблема действительно заключалась в моей обработке указателя стека, как уже указывал @Peter Cordes. Вот полная версия исправленного кода. Спасибо за помощь!
BITS 64
%include "const.asm"
%define u(x) __utf16__(x)
%define w(x) __utf32__(x)
; Externs
extern InternetOpenA
extern GetLastError
extern GetStdHandle
extern WriteFile
extern ExitProcess
section .data
curProc db 0
hInternet dq 0
lpszAgent db "Mozilla/5.0", 0
NtlpBuffer: db 'Hello, World!', 0
NtnNBytesToWrite: dq 0eh
section .text
global Start
Start:
push rbp
mov rbp, rsp
sub rsp, 48
lea rcx, [REL lpszAgent] ; lpszAgentW (WIDE STRING)
mov rdx, 4 ; dwAccessType (DWORD)
mov r8, NULL ; lpszProxy (DWORD)
mov r9, NULL ; pszProxyBypassW (DWORD)
mov dword [rsp + 4 * 8], NULL ; dwFlags (DWORD)
call InternetOpenA
mov [REL hInternet], rax ; hInternet parameter
cmp rax, NULL
je error_handler
jmp exit
error_handler:
call GetLastError ; Load more error info
jmp exit
exit:
call print
xor rcx, rcx
call ExitProcess
print:
sub rsp, 40
mov ecx, -11
call GetStdHandle
mov rcx, rax
lea rdx, [rel NtlpBuffer]
mov r8, [REL NtnNBytesToWrite]
mov r9, NULL
mov qword [rsp + 32], 00h
call WriteFile
add rsp, 40
ret
Пожалуйста, примите ваш ответ как ответ, если это решит проблему.
-1073741819
это0xC0000005