Linux NASM:如何使用英特尔64位汇编创建/处理基本bmp文件?
如何创建/处理简单的bmp文件,仅使用“英特尔64位汇编”和“nasm汇编”将其填充为一种颜色?包含此类操作的步骤包括:Linux NASM:如何使用英特尔64位汇编创建/处理基本bmp文件?,linux,assembly,x86,nasm,x86-64,Linux,Assembly,X86,Nasm,X86 64,如何创建/处理简单的bmp文件,仅使用“英特尔64位汇编”和“nasm汇编”将其填充为一种颜色?包含此类操作的步骤包括: 创建具有固定值的bmp文件头(下面解释特定字段) 创建包含足够空间的缓冲区-每像素三个字节(一种颜色=红色+绿色+蓝色) 打开/创建文件 填满缓冲区 将头文件写入文件 将缓冲区写入文件 关闭文件 退出程序 广告2:这有点棘手-如果每行的像素数不能被4整除,程序必须用0xFF填充缺少的字节。在这里,我特意创建了一张201x201的图片。在这个例子中,我们可以看到每行有3*201
section .text
global _start ;must be declared for linker (ld)
_start: ;tell linker entry point
;#######################################################################
;### This program creates empty bmp file - 64 bit version ##############
;#######################################################################
;### main ##############################################################
;#######################################################################
; open file
mov rax,85 ;system call number - open/create file
mov rdi,msg ;file name
;flags
mov rsi,111111111b ;mode
syscall ;call kernel
; save file descriptor
mov r8, rax
; write headline to file
mov rax, 1 ;system call number - write
mov rdi, r8 ;load file desc
mov rsi, bmpheadline ;load adress of buffer to write
mov rdx, 54 ;load number of bytes
syscall ;call kernel
mov rbx, 201 ;LOOPY counter
mov rdx, empty_space ;load address of buffer (space allocated for picture pixels)
LOOPY:
mov rcx, 201 ;LOOPX counter
LOOPX:
mov byte [rdx+0], 0x00 ;BLUE
mov byte [rdx+1], 0xFF ;GREEN
mov byte [rdx+2], 0xFF ;RED
dec rcx ;decrease counter_x
add rdx, 3 ;move address pointer by 3 bytes (1 pixel = 3 bytes, which we just have written)
cmp rcx, 0 ;check if counter is 0
jne LOOPX ;if not jump to LOOPX
dec rbx ;decrease counter_y
mov byte [rdx], 0xFF ;additional byte per row
inc rdx ;increase address
cmp rbx, 0 ;check if counter is 0
jne LOOPY ;if not jump to LOOPY
; write content to file
mov rax, 1 ;system call number - write
mov rdi, r8 ;load file desc
mov rsi, empty_space ;load adress of buffer to write
mov rdx, 121404 ;load number of bytes
syscall ;call kernel
; close file
mov rax, 3 ;system call number - close
mov rdi, r8 ;load file desc
syscall ;call kernel
; exit program
mov rax,60 ;system call number - exit
syscall ;call kernel
section .data
msg db 'filename.bmp',0x00 ;name of out file, 0x00 = end of string
bmpheadline db 0x42,0x4D,0x72,0xDA,0x01,0x00,0x00,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0xC9,0x00,0x00,0x00,0xC9,0x00,0x00,0x00,0x01,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x3C,0xDA,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
section .bss ;this section is responsible for preallocated block of memory of fixed size
empty_space: resb 121404 ;preallocation of 121404 bytes
这里是bmp标题的解释(更多信息请点击此链接:)
下面是rbraun答案的改进版本。这应该是codereview.SE>上的问答。这可能更适合。代码审阅:BMP标题格式描述内容不必分开。您可以在单独的行中写入
db“BM”
dd 54+201*(201+1)*3等,而不是db 0x42,0x4D,0x72,…
。所有这些字节都是连续的,因为asm就是这样工作的。您甚至可以在标题的不同部分放置单独的标签。在前两个示例之后,您可以停止自己手动将代码编码为十六进制字节,让汇编程序从最具可读性的表达式中为您完成这项工作。e、 g.dd201
而不是db 0xC9,0,0,0
,因为x86是小端数。mov-eax,85
比mov-rax,85
效率更高(代码大小更小)。并非所有与NASM兼容的汇编程序都能为您优化这一点。对于所有其他对非64位对象进行操作的指令也是如此。(例如,移动文件描述符。无需64位mov。)msg
是文件名标签的糟糕选择。称它为fname
或其他什么。你的循环有点笨重。在内部循环的底部,使用addrdx,3
/dec-ecx/jne-LOOPX
。DEC和SUB根据结果设置ZF(就像几乎所有修改标志的指令一样),因此您不需要单独的CMP来实现倒计时循环。我投票结束这个问题,因为它是代码高尔夫。@RossRidge:我认为这更像是尝试编写一个示例(可能会继续)。很明显,OP并没有试图用问题的自我答案来解决任何问题。为了它的价值,我想通过使用单色位图(或RLE压缩,但它没有得到很好的支持)来改进代码。我还将使用NASM创建整个BMP文件,这样它就可以通过一个write
系统调用或NASM-f bin-o filename.BMP
@RossRidge:我考虑过将缓冲区放在数据段的头后(我猜你建议用TIMES db…
填充它),但我认为这是一个更好的例子,说明如果这不是一个独立的程序,你可能会做什么。另外,我不想让二进制文件膨胀。不过,直接写出BMP的nasm-f bin
是一个好主意。使用单色位图,您可以使图像数据完全为0,并且应该有足够的空间来安排事情,这样它实际上就不是可执行文件的一部分,而是零字段。@RossRidge:oh-hmm,使用某种链接器节/段技巧将初始化数据页放在BSS页的旁边?美好的是的,那完全行得通:)我根本没有使用BMP,只是稍微使用了一点,1bpp位图总是黑/白的,所以我没有意识到。
;### File Header - 14 bytes
;#######################################################################
;### bfType, 2 bytes, The characters "BM"
;### 0x42,0x4D = "B","M"
;###
;### bfSize, 4 bytes, The size of the file in bytes
;### 0x72,0xDA,0x01,0x00 => 0x00,0x01,0xDA,0x72 = 0x1DA72 = 121458 bytes
;### 121458 = 54 + 201 * (201 + 1) * 3
;###
;### Comment:
;### We want to create file 201x201, that means 201 rows and 201 columns
;### meaning each row will take 201*3 = 603 bytes
;###
;### According to BMP file specification each such row must be adjusted
;### so its size is dividable by 4, this gives us plus 1 byte for each
;### row.
;###
;###
;### bfReserved1, 2 bytes, Unused - must be zero
;### 0x00,0x00
;###
;### bfReserved2, 2 bytes, Unused - must be zero
;### 0x00,0x00
;###
;### bfOffBits, 4 bytes, Offset to start of Pixel Data
;### 0x36,0x00,0x00,0x00 = 54 bytes
;###
;### Image Header - 40 bytes
;#######################################################################
;### biSize 4 Header Size - Must be at least 40
;### 0x28,0x00,0x00,0x00 = 40
;###
;### biWidth 4 Image width in pixels
;### 0xC9,0x00,0x00,0x00 = 201
;###
;### biHeight 4 Image height in pixels
;### 0xC9,0x00,0x00,0x00 = 201
;###
;### biPlanes 2 Must be 1
;### 0x01,0x00
;###
;### biBitCount 2 Bits per pixel - 1, 4, 8, 16, 24, or 32
;### 0x18,0x00 = 24
;###
;### biCompression 4 Compression type (0 = uncompressed)
;### 0x00,0x00,0x00,0x00
;###
;### biSizeImage 4 Image Size - may be zero for uncompressed images
;### 0x3C,0xDA,0x01,0x00 => 0x00,0x01,0xDA,0x3C = 121404 bytes
;###
;### biXPelsPerMeter 4 Preferred resolution in pixels per meter
;### 0x00,0x00,0x00,0x00
;###
;### biYPelsPerMeter 4 Preferred resolution in pixels per meter
;### 0x00,0x00,0x00,0x00
;###
;### biClrUsed 4 Number Color Map entries that are actually used
;### 0x00,0x00,0x00,0x00
;###
;### biClrImportant 4 Number of significant colors
;### 0x00,0x00,0x00,0x00
;###
DEFAULT REL ; default to RIP-relative addressing for static data
;#######################################################################
;### This program creates empty bmp file - 64 bit version ##############
section .rodata ; read-only data is the right place for these, not .data
BMPcols equ 2019
BMProws equ 2011
; 3 bytes per pixel, with each row padded to a multiple of 4B
BMPpixbytes equ 3 * BMProws * ((BMPcols + 3) & ~0x3)
;; TODO: rewrite this header with separate db and dd directives for the different fields. Preferably in terms of assembler-constant width and height
ALIGN 16 ; for efficient rep movs
bmpheader:
;; BMP is a little-endian format, so we can use dd and stuff directly instead of encoding the bytes ourselves
bfType: dw "BM"
bfSize: dd BMPpixbytes + bmpheader_len ; size of file in bytes
dd 0 ; reserved
bfOffBits: dd bmpheader_len ; yes we can refer to stuff that's defined later.
biSize: dd 40 ; header size, min = 40
biWidth: dd BMPcols
biHeight: dd BMProws
biPlanes: dw 1 ; must be 1
biBitCount: dw 24 ; bits per pixel: 1, 4, 8, 16, 24, or 32
biCompression: dd 0 ; uncompressed = 0
biSizeImage: dd BMPpixbytes ; Image Size - may be zero for uncompressed images
biXPelsPerMeter: dd 0 ; Preferred resolution in pixels per meter
biYPelsPerMeter: dd 0 ; Preferred resolution in pixels per meter
biClrUsed: dd 0 ; Number Color Map entries that are actually used
biClrImportant: dd 0 ; Number of significant colors
bmpheader_len equ $ - bmpheader ; Let the assembler calculate this for us. Should be 54. `.` is the current position
; output filename is hard-coded. Checking argc / argv is left as an exercise for the reader.
; Of course it would be even easier to be more Unixy and just always write to stdout, so the user could redirect
fname db 'filename.bmp',0x00 ;name of out file, 0x00 = end of string
section .bss ;this section is responsible for fixed size preallocated blocks
bmpbuf: resb 54 + BMPpixbytes ; static buffer big enough to hold the whole file (including header).
bmpbuf_len equ $ - bmpbuf
section .text
global _start ;make the symbol externally visible
_start: ;The linker looks for this symbol to set the entry point
;#######################################################################
;### main ##############################################################
; creat(fname, 0666)
mov eax,85 ; SYS_creat from /usr/include/x86_64-linux-gnu/asm/unistd_64.h
;mov edi, fname ;file name string. Static data is always in the low 2G, so you can use 32bit immediates.
lea rdi, [fname] ; file name, PIC version. We don't need [rel fname] since we used DEFAULT REL.
; Ubuntu 16.10 defaults to enabling position-independent executables that can use ASLR, but doesn't require it the way OS X does.)
;creat doesn't take flags. It's equivalent to open(path, O_CREAT|O_WRONLY|O_TRUNC, mode).
mov esi, 666o ;mode in octal, to be masked by the user's umask
syscall ; eax = fd or -ERRNO
test eax,eax ; error checking on system calls.
js .handle_error ; We don't print anything, so run under strace to see what happened.
;;; memcpy the BMP header to the start of our buffer.
;;; SSE loads/stores would probably be more efficient for such a small copy
mov edi, bmpbuf
mov esi, bmpheader
;Alternative: rep movsd or movsq may be faster.
;mov ecx, bmpheader_len/4 + 1 ; It's not a multiple of 4, but copy extra bytes because MOVSD is faster
mov ecx, bmpheader_len
rep movsb
; edi now points to the first byte after the header, where pixels should be stored
; mov edi, bmpbuffer+bmpheader_len might let out-of-order execution get started on the rest while rep movsb was still running, but IDK.
;######### main loop
mov ebx, BMProws
.LOOPY: ; do{
mov ecx, BMPcols ; Note the use of a macro later to decide whether we need padding at the end of each row or not, so arbitrary widths should work.
.LOOPX: ; do{
mov dword [rdi], (0xFF <<16) | (0xFF <<8) | 0x00 ;RED=FF, GREEN=FF, BLUE=00
; stores one extra byte, but we overlap it with the next store
add rdi, 3 ;move address pointer by 3 bytes (1 pixel = 3 bytes, which we just have written)
dec ecx
jne .LOOPX ; } while(--x != 0)
; end of inner loop
%if ((BMPcols * 3) % 4) != 0
; Pad the row to a multiple of 4B
mov dword [rdi], 0xFFFFFFFF ; might only need a byte or word store, but another dword store that we overlap is fine as long as it doesn't go off the end of the buffer
add rdi, 4 - (BMPcols * 3) % 4 ; advance to a 4B boundary
%endif
dec ebx
jne .LOOPY ; } while(--y != 0)
;##### Write out the buffer to the file
; fd is still where we left it in RAX.
; write and close calls both take it as the first arg,
; and the SYSCALL ABI only clobbers RAX, RCX, and R11, so we can just put it in EDI once.
mov edi, eax ; fd
; write content to file: write(fd, bmpbuf, bmpbuf_len)
mov eax, 1 ;SYS_write
lea rsi, [bmpbuf] ;buffer.
; We already have enough info in registers that reloading this stuff as immediate constants isn't necessary, but it's much more readable and probably at least as efficient anyway.
mov edx, bmpbuf_len
syscall
; close file
mov eax, 3 ;SYS_close
; fd is still in edi
syscall
.handle_error:
; exit program
mov rax,60 ;system call number - exit
syscall