3 проблемы:
GNU / Linux с использованием объектных файлов ELF не не украшает / не меняет имена C с начальным подчеркиванием. Используйте call printf
, а не _printf
(В отличие от MacOS X, который украшает символы символом _
; имейте это в виду, если вы смотрите учебные пособия для других ОС. Windows также использует другое соглашение о вызовах, но только 32-разрядные Windows изменяют имена с помощью _
или других декораций, которые кодируют выбор соглашения о вызовах.)
Вы не сказали ld
связать libc , и вы сами не определили printf
, поэтому вы не дали компоновщику никаких входных файлов, содержащих определение для этого символа. printf
является библиотечной функцией, определенной в libc.so, и в отличие от внешнего интерфейса GCC, ld
не включает ее автоматически.
_start
не является функцией, вы не можете ret
из нее. RSP указывает на argc
, а не на обратный адрес. Вместо этого определите main
, если хотите, чтобы это была нормальная функция.
Связывайтесь с gcc -no-pie -nostartfiles hello.o -o hello
, если вам нужен динамический исполняемый файл, который предоставляет собственный _start
вместо main
, но все еще использует libc.
Это безопасно для динамических исполняемых файлов в GNU / Linux, потому что glibc может запускать свои функции init через ловушки динамического компоновщика . Это не безопасно в Cygwin, где его libc инициализируется только вызовами из его стартового файла CRT (которые делают это перед вызовом main
).
Используйте call exit
для выхода, вместо того, чтобы делать системный вызов _exit
напрямую, если вы используете printf
; это позволяет libc сбрасывать любые буферизованные выходные данные. (Если вы перенаправите вывод в файл, stdout будет иметь полную буферизацию по сравнению с буферизованной строкой на терминале.)
-static
не будет безопасным; в статическом исполняемом файле динамический компоновщик не запускается до вашего _start
, поэтому у libc нет возможности инициализировать себя, если вы не вызовете функции вручную. Это возможно, но обычно не рекомендуется.
Существуют другие реализации libc, которым не нужны никакие функции инициализации, вызываемые до printf
/ malloc
/ других функций. В glibc такие вещи, как буферы stdio, выделяются во время выполнения. (Это раньше имело место для MUSL libc , но это, очевидно, уже не так, согласно комментарию Флориана к этому ответу.)
Обычно, если вы хотите чтобы использовать функции libc, было бы неплохо определить функцию main
вместо собственной точки входа _start
. Тогда вы можете просто связаться с gcc
как обычно, без специальных опций.
См. Какие части этого ассемблерного кода HelloWorld необходимы для написания программы на ассемблере? для этого и для версии, которая использует системные вызовы Linux напрямую, без libc. [одна тысяча сто восемьдесят два]
Если вы хотите, чтобы ваш код работал в исполняемом файле PIE, как gcc делает по умолчанию (без --no-pie
) в недавних дистрибутивах, вам понадобится call printf wrt ..plt
.
В любом случае, вы должны использовать lea rsi, [rel message]
, потому что REA-относительный LEA более эффективен, чем mov r64, imm64
с 64-битным абсолютным адресом. (В позиционно-зависимом коде лучшим вариантом для помещения статического адреса в 64-битный регистр является 5-байтовый mov esi, message
, поскольку известно, что статические адреса в исполняемых файлах, отличных от PIE, находятся на низком 2 ГБ виртуального адресного пространства, и, таким образом, работают как 32-битные исполняемые файлы с расширенными знаками или нулями. Но REA-относительный LEA не намного хуже и работает везде.)
;;; Defining your own _start but using libc
;;; works on Linux for non-PIE executables
default rel ; Use RIP-relative for [symbol] addressing modes
extern printf
extern exit ; unlike _exit, exit flushes stdio buffers
section .text
global _start
_start:
;; RSP is already aligned by 16 on entry at _start, unlike in functions
lea rdi, [format] ; argument #1 or better mov edi, format
lea rsi, [message] ; argument #2
xor eax, eax ; no FP args to the variadic function
call printf ; for a PIE executable: call printf wrt ..plt
xor edi, edi ; arg #1 = 0
call exit ; exit(0)
; exit definitely does not return
section .rodata ;; read-only data can go in .rodata instead of read-write .data
message: db "Hello, world!", 0
format: db "%s", 0xa, 0
Собираются нормально, связаны с gcc -no-pie -nostartfiles hello.o
. Это исключает файлы запуска CRT, которые обычно определяют _start
, который делает некоторые вещи перед вызовом main
. Функции инициализации Libc вызываются из ловушек динамического компоновщика, поэтому printf
можно использовать.
Это было бы не так с gcc -static -nostartfiles hello.o
. Я включил примеры того, что происходит, если вы используете неправильные опции:
peter@volta:/tmp$ nasm -felf64 nopie-start.asm
peter@volta:/tmp$ gcc -no-pie -nostartfiles nopie-start.o
peter@volta:/tmp$ ./a.out
Hello, world!
peter@volta:/tmp$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0cd1cd111ba0c6926d5d69f9191bdf136e098e62, not stripped
# link error without -no-pie because it doesn't automatically make PLT stubs
peter@volta:/tmp$ gcc -nostartfiles nopie-start.o
/usr/bin/ld: nopie-start.o: relocation R_X86_64_PC32 against symbol `printf@@GLIBC_2.2.5' can not be used when making a PIE object; recompile with -fPIC
/usr/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
# runtime error with -static
peter@volta:/tmp$ gcc -static -no-pie -nostartfiles nopie-start.o -o static_start-hello
peter@volta:/tmp$ ./static_start-hello
Segmentation fault (core dumped)
main
вместо _start
(И упрощая с помощью puts
] вместо printf
.)
default rel ; Use RIP-relative for [symbol] addressing modes
extern puts
section .text
global main
main:
sub rsp, 8 ;; RSP was 16-byte aligned *before* a call pushed a return address
;; RSP is now 16-byte aligned, ready for another call
mov edi, message ; argument #1, optimized to use non-PIE-only move imm32
call puts
add rsp, 8 ; restore the stack
xor eax, eax ; return 0
ret
section .rodata
message: db "Hello, world!", 0 ; puts appends a newline
puts
в значительной степени точно реализует printf("%s\n", string)
; Компиляторы C сделают эту оптимизацию за вас, но в asm вы должны сделать это сами.
Связь с gcc -no-pie hello.o
или даже статическая связь с использованием gcc -no-pie -static hello.o
. Код запуска CRT будет вызывать функции инициализации glibc.
peter@volta:/tmp$ nasm -felf64 nopie-main.asm
peter@volta:/tmp$ gcc -no-pie nopie-main.o
peter@volta:/tmp$ ./a.out
Hello, world!
# link error if you leave out -no-pie because of the imm32 absolute address
peter@volta:/tmp$ gcc nopie-main.o
/usr/bin/ld: nopie-main.o: relocation R_X86_64_32 against `.rodata' can not be used when making a PIE object; recompile with -fPIC
/usr/bin/ld: final link failed: nonrepresentable section on output
collect2: error: ld returned 1 exit status
main
- это функция, поэтому перед повторным вызовом функции необходимо повторно выровнять стек. Фиктивный толчок также является допустимым способом выравнивания стека при вводе функции, но add
/ sub rsp, 8
более понятны.
Альтернативой является jmp puts
, чтобы вызвать его, так что возвращаемое значение main
будет тем, что возвращает puts
. В этом случае вы не должны изменять сначала rsp
: вы просто переходите к puts
с вашим обратным адресом, все еще находящимся в стеке, точно так же, как если бы ваш вызывающий абонент вызвал puts
.
main
(Вы можете сделать PIE, который определяет свой собственный _start
. Это оставлено в качестве упражнения для читателя.)
[114 ]
peter@volta:/tmp$ nasm -felf64 pie.asm
peter@volta:/tmp$ gcc pie.o
peter@volta:/tmp$ ./a.out
Hello, world!
peter@volta:/tmp$ file a.out
a.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b27e6032f955d628a542f6391b50805c68541fb9, not stripped
Чтобы протестировать, если объединенные ячейки покрывают более затем одну строку:
cellRange = ExcelApplication.Cells(rowIndex, columnIndex)
mergedColumns = cellRange.MergeArea.Rows.Count
if mergedColumns > 1 then ' merged
Спасибо за Ваши ответы. eJames я думаю, что Вы ответили на все, которое я хотел знать, как обнаружить объединенные ячейки и также FolderName / присвоение ItemName. Еще раз спасибо.
Я могу записать CSV без проблемы после более близкого контроля, я заметил, что также ячейки FOLDERn объединяют столбцы (A-C) так, вероятно, я могу также свериться с кодом господина Lucky:
mergedColumns = cellRange.MergeArea.Columns.Count
И если mergedColumns> 1 затем это - имя папки.
Можно использовать:
if (Cell.MergeCells) Then ...
определить, является ли ячейка частью объединенного диапазона, и
Cell.MergeArea.Cells(1,1).value
получить доступ к значению, содержавшемуся в том объединенном диапазоне.
Вот некоторый пример кода для запущения Вас. Я опустил детали фактической записи файла CSV:
Public Sub MakeCSV()
Dim rowIndex As Integer
Dim itemColumn As Range
Dim dataColumn As Range
Dim itemCell As Range
Dim FolderName As String
Dim ItemName As String
Dim CSVLine As String
Set itemColumn = Range("A2:A100")
Set dataColumn = Range("B2:B100")
For rowIndex = 1 To dataColumn.Rows.Count
If dataColumn.Cells(rowIndex, 1).value = "" Then
Rem // determine the current folder name
FolderName = itemColumn.Cells(rowIndex, 1).value
Else
Rem // determine the item name (accounting for merged cells)
Set itemCell = itemColumn.Cells(rowIndex, 1)
If itemCell.MergeCells Then
ItemName = itemCell.MergeArea.Cells(1, 1).value
Else
ItemName = itemCell.value
End If
Rem // write a line to the CSV file
CSVLine = dataColumn.Cells(rowIndex, 1).value
CSVLine = CSVLine & "," & ItemName
CSVLine = CSVLine & "," & FolderName
End If
Next rowIndex
End Sub