Python ctypes как прочитать байт из массива символов, переданного в NASM - PullRequest
0 голосов
/ 22 января 2019

ОБНОВЛЕНИЕ: я решил эту проблему с помощью ответа Марка Толонена ниже. Вот решение (но я озадачен одной вещью):

Я начинаю со строки кодирования, показанной в ответе Марка Толонена ниже (UTF-8):

CA_f1 = (ctypes.c_char_p * len(f1))(*(name.encode() for name in f1))

При отключенных оптимизациях я всегда сохраняю rcx в переменной памяти при входе. Позже в программе, когда мне нужно использовать указатель в rcx, я читаю его из памяти. Это работает для одного указателя, но не работает для доступа к массиву указателей, который Марк Толонен показал ниже; может быть, это потому, что это массив указателей, а не просто один указатель. Это работает, если я сохраняю rcx в r15 при входе, и вниз по течению в программе это работает так:

;To access the first char of the first name pair: 

xor rax,rax
mov rdx,qword[r15]
movsx eax,BYTE[rdx]
ret

;To access the second char of the second name pair: 

mov rdx,qword[r15+8]
movsx eax,BYTE[rdx+1]

Это не проблема, потому что я обычно храню как можно больше переменных в регистрах; иногда не хватает регистров, поэтому приходится прибегать к хранению некоторых в памяти. Теперь при обработке строк я всегда буду резервировать r15 для хранения указателя, переданного в rcx, если это массив указателей.

Любое понимание того, почему ячейка памяти не работает?

**** КОНЕЦ ОТВЕТА ****

Я новичок в обработке строк в NASM и передаю строку из ctypes. Строковые данные считываются из текстового файла (Windows .txt), используя следующую функцию Python:

with open(fname, encoding = "utf8") as f1:
        for item in f1:
            item = item.lstrip()
            item = item.rstrip()
            return_data.append(item)
    return return_data

Файл .txt содержит список имен и фамилий, разделенных символами перевода строки.

Я передаю указатель c_char_p в dll NASM, используя ctypes. Указатель создается с помощью этого:

CA_f1 = (ctypes.c_char_p * len(f1))()

Visual Studio подтверждает, что это указатель на строку байтов длиной 50 ИМЕН, что может быть проблемой, мне нужны байты, а не элементы списка. Затем я передаю его, используя синтаксис ctypes:

CallName.argtypes = [ctypes.POINTER(ctypes.c_char_p),ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double)]

ОБНОВЛЕНИЕ: перед передачей строки теперь я преобразую список в строку, подобную этой:

f1_x = ' '.join(f1)

Теперь VS показывает указатель на строку длиной 558 байт, что правильно, но я все еще не могу прочитать байт.

В моей программе NASM я проверяю ее, считывая случайный байт в al, используя следующий код:

lea rdi,[rel f1_ptr]
mov rbp,qword [rdi] ; Pointer
xor rax,rax
mov al,byte[rbp+1]

Но возвращаемое значение в rax равно 0.

Если я создаю локальный строковый буфер следующим образом:

name_array: db "Margaret Swanson"

Я могу прочитать это так:

mov rdi,name_array
xor rax,rax
mov al,[rdi]

Но не из указателя, переданного в DLL.

Вот полный код для простого, воспроизводимого примера в NASM. Прежде чем передать его в NASM, я проверил случайные байты, и они - то, что я ожидаю, поэтому я не думаю, что это кодирование.

[BITS 64]
[default rel]

extern malloc, calloc, realloc, free
global Main_Entry_fn
export Main_Entry_fn
global FreeMem_fn
export FreeMem_fn

section .data align=16
f1_ptr: dq 0
f1_length: dq 0
f2_ptr: dq 0
f2_length: dq 0
data_master_ptr: dq 0

section .text

String_Test_fn:
;______

lea rdi,[rel f1_ptr]
mov rbp,qword [rdi]
xor rax,rax
mov al,byte[rbp+10]
ret

;__________
;Free the memory

FreeMem_fn:
sub rsp,40
call free
add rsp,40
ret

; __________
; Main Entry

Main_Entry_fn:
push rdi
push rbp
mov [f1_ptr],rcx
mov [f2_ptr],rdx

mov [data_master_ptr],r8
lea rdi,[data_master_ptr]
mov rbp,[rdi]
xor rcx,rcx
movsd xmm0,qword[rbp+rcx]
cvttsd2si rax,xmm0
mov [f1_length],rax
add rcx,8
movsd xmm0,qword[rbp+rcx]
cvttsd2si rax,xmm0
mov [f2_length],rax
add rcx,8

call String_Test_fn

pop rbp
pop rdi
ret

ОБНОВЛЕНИЕ 2:

В ответ на запрос вот обертка ctypes для использования:

def Read_Data():

    Dir= "[FULL PATH TO DATA]"

    fname1 = Dir + "Random Names.txt"
    fname2 = Dir + "Random Phone Numbers.txt"

    f1 = Trans_02_Data.StrDataRead(fname1)
    f2 = Trans_02_Data.StrDataRead(fname2)
    f2_Int = [  int(numeric_string) for numeric_string in f2]
    StringTest_asm(f1, f2_Int)

def StringTest_asm(f1,f2):

    f1.append("0")

    f1_x = ' '.join(f1)
    f1_x[0].encode(encoding='UTF-8',errors='strict')

    Input_Length_Array = []
    Input_Length_Array.append(len(f1))
    Input_Length_Array.append(len(f2*8))

    length_array_out = (ctypes.c_double * len(Input_Length_Array))(*Input_Length_Array)

    CA_f1 = (ctypes.c_char_p * len(f1_x))() #due to SO research
    CA_f2 = (ctypes.c_double * len(f2))(*f2)
    hDLL = ctypes.WinDLL("C:/NASM_Test_Projects/StringTest/StringTest.dll")
    CallName = hDLL.Main_Entry_fn
    CallName.argtypes = [ctypes.POINTER(ctypes.c_char_p),ctypes.POINTER(ctypes.c_double),ctypes.POINTER(ctypes.c_double)]
    CallName.restype = ctypes.c_int64

    Free_Mem = hDLL.FreeMem_fn
    Free_Mem.argtypes = [ctypes.POINTER(ctypes.c_double)]
    Free_Mem.restype = ctypes.c_int64
    start_time = timeit.default_timer()

    ret_ptr = CallName(CA_f1,CA_f2,length_array_out)

    abc = 1 #Check the value of the ret_ptr, should be non-zero   

1 Ответ

0 голосов
/ 23 января 2019

Ваш код чтения имени вернет список строк Unicode.Следующее закодирует список строк Unicode в массив строк, которые будут переданы функции, принимая POINTER(c_char_p):

>>> import ctypes
>>> names = ['Mark','John','Craig']
>>> ca = (ctypes.c_char_p * len(names))(*(name.encode() for name in names))
>>> ca
<__main__.c_char_p_Array_3 object at 0x000001DB7CF5F6C8>
>>> ca[0]
b'Mark'
>>> ca[1]
b'John'
>>> ca[2]
b'Craig'

Если ca передается вашей функции в качестве первого параметра,адрес этого массива будет в rcx для x64 соглашения о вызовах .Следующий код C и его разборка показывает, как компилятор Microsoft VS2017 читает его:

код DLL (test.c)

#define API __declspec(dllexport)

int API func(const char** instr)
{
    return (instr[0][0] << 16) + (instr[1][0] << 8) + instr[2][0];
}

Разборка (скомпилированомои комментарии добавлены)

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1

include listing.inc

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  func
; Function compile flags: /Ogtpy
; File c:\test.c
_TEXT   SEGMENT
instr$ = 8
func    PROC

; 5    :     return (instr[0][0] << 16) + (instr[1][0] << 8) + instr[2][0];

  00000 48 8b 51 08      mov     rdx, QWORD PTR [rcx+8]  ; address of 2nd string
  00004 48 8b 01         mov     rax, QWORD PTR [rcx]    ; address of 1st string
  00007 48 8b 49 10      mov     rcx, QWORD PTR [rcx+16] ; address of 3rd string
  0000b 44 0f be 02      movsx   r8d, BYTE PTR [rdx]     ; 1st char of 2nd string, r8d=4a
  0000f 0f be 00         movsx   eax, BYTE PTR [rax]     ; 1st char of 1st string, eax=4d
  00012 0f be 11         movsx   edx, BYTE PTR [rcx]     ; 1st char of 3rd string, edx=43
  00015 c1 e0 08         shl     eax, 8                  ; eax=4d00
  00018 41 03 c0         add     eax, r8d                ; eax=4d4a
  0001b c1 e0 08         shl     eax, 8                  ; eax=4d4a00
  0001e 03 c2            add     eax, edx                ; eax=4d4a43

; 6    : }

  00020 c3               ret     0
func    ENDP
_TEXT   ENDS
END

Код Python (test.py)

from ctypes import *

dll = CDLL('test')
dll.func.argtypes = POINTER(c_char_p),
dll.restype = c_int

names = ['Mark','John','Craig']
ca = (c_char_p * len(names))(*(name.encode() for name in names))
print(hex(dll.func(ca)))

Вывод:

0x4d4a43

Это правильные коды ASCII для «M», «J» и «C».

...