📄 pxelinux.asm
字号:
;unmangle_name: call strcpy dec di ; Point to final null byte ret;; pxenv;; This is the main PXENV+/!PXE entry point, using the PXENV+; calling convention. This is a separate local routine so; we can hook special things from it if necessary.;pxenv:.jump: call 0:pxe_thunk ; Default to calling the thunk cld ; Make sure DF <- 0 ret; Must be after function def due to NASM bugPXENVEntry equ pxenv.jump+1;; pxe_thunk;; Convert from the PXENV+ calling convention (BX, ES, DI) to the !PXE; calling convention (using the stack.);; This is called as a far routine so that we can just stick it into; the PXENVEntry variable.;pxe_thunk: push es push di push bx.jump: call 0:0 add sp,byte 6 cmp ax,byte 1 cmc ; Set CF unless ax == 0 retf; Must be after function def due to NASM bugPXEEntry equ pxe_thunk.jump+1;; getfssec: Get multiple clusters from a file, given the starting cluster.;; In this case, get multiple blocks from a specific TCP connection.;; On entry:; ES:BX -> Buffer; SI -> TFTP socket pointer; CX -> 512-byte block count; 0FFFFh = until end of file; On exit:; SI -> TFTP socket pointer (or 0 on EOF); CF = 1 -> Hit EOF;getfssec: push si push fs mov di,bx mov bx,si mov ax,pktbuf_seg mov fs,ax movzx ecx,cx shl ecx,TFTP_BLOCKSIZE_LG2 ; Convert to bytes jz .hit_eof ; Nothing to do? .need_more: push ecx movzx eax,word [bx+tftp_bytesleft] cmp ecx,eax jna .ok_size mov ecx,eax jcxz .need_packet ; No bytes available?.ok_size: mov ax,cx ; EAX<31:16> == ECX<31:16> == 0 mov si,[bx+tftp_dataptr] sub [bx+tftp_bytesleft],cx fs rep movsb ; Copy from packet buffer mov [bx+tftp_dataptr],si pop ecx sub ecx,eax jnz .need_more.hit_eof: pop fs pop si ; Is there anything left of this? mov eax,[si+tftp_filesize] sub eax,[si+tftp_filepos] jnz .bytes_left ; CF <- 0 cmp [si+tftp_bytesleft],ax jnz .bytes_left ; CF <- 0 ; The socket is closed and the buffer drained ; Close socket structure and re-init for next user call free_socket stc.bytes_left: ret;; No data in buffer, check to see if we can get a packet...;.need_packet: pop ecx mov eax,[bx+tftp_filesize] cmp eax,[bx+tftp_filepos] je .hit_eof ; Already EOF'd; socket already closed pushad push es mov si,bx call get_packet pop es popad jmp .need_more;; Get a fresh packet; expects fs -> pktbuf_seg and ds:si -> socket structure;get_packet: mov ax,ds mov es,ax .packet_loop: ; Start by ACKing the previous packet; this should cause the ; next packet to be sent. mov cx,PKT_RETRY mov word [PktTimeout],PKT_TIMEOUT.send_ack: push cx ; <D> Retry count mov ax,[si+tftp_lastpkt] call ack_packet ; Send ACK ; We used to test the error code here, but sometimes ; PXE would return negative status even though we really ; did send the ACK. Now, just treat a failed send as ; a normally lost packet, and let it time out in due ; course of events..send_ok: ; Now wait for packet. mov dx,[BIOS_timer] ; Get current time mov cx,[PktTimeout].wait_data: push cx ; <E> Timeout push dx ; <F> Old time mov bx,[si+tftp_pktbuf] mov [pxe_udp_read_pkt.buffer],bx mov [pxe_udp_read_pkt.buffer+2],fs mov [pxe_udp_read_pkt.buffersize],word PKTBUF_SIZE mov eax,[si+tftp_remoteip] mov [pxe_udp_read_pkt.sip],eax mov eax,[MyIP] mov [pxe_udp_read_pkt.dip],eax mov ax,[si+tftp_remoteport] mov [pxe_udp_read_pkt.rport],ax mov ax,[si+tftp_localport] mov [pxe_udp_read_pkt.lport],ax mov di,pxe_udp_read_pkt mov bx,PXENV_UDP_READ push si ; <G> call pxenv pop si ; <G> and ax,ax jz .recv_ok ; No packet, or receive failure mov dx,[BIOS_timer] pop ax ; <F> Old time pop cx ; <E> Timeout cmp ax,dx ; Same time -> don't advance timeout je .wait_data ; Same clock tick loop .wait_data ; Decrease timeout pop cx ; <D> Didn't get any, send another ACK shl word [PktTimeout],1 ; Exponential backoff loop .send_ack jmp kaboom ; Forget it....recv_ok: pop dx ; <F> pop cx ; <E> cmp word [pxe_udp_read_pkt.buffersize],byte 4 jb .wait_data ; Bad size for a DATA packet mov bx,[si+tftp_pktbuf] cmp word [fs:bx],TFTP_DATA ; Not a data packet? jne .wait_data ; Then wait for something else mov ax,[si+tftp_lastpkt] xchg ah,al ; Host byte order inc ax ; Which packet are we waiting for? xchg ah,al ; Network byte order cmp [fs:bx+2],ax je .right_packet ; Wrong packet, ACK the packet and then try again ; This is presumably because the ACK got lost, ; so the server just resent the previous packet mov ax,[fs:bx+2] call ack_packet jmp .send_ok ; Reset timeout.right_packet: ; It's the packet we want. We're also EOF if the size < blocksize pop cx ; <D> Don't need the retry count anymore mov [si+tftp_lastpkt],ax ; Update last packet number movzx ecx,word [pxe_udp_read_pkt.buffersize] sub cx,byte 4 ; Skip TFTP header ; If this is a zero-length block, don't mess with the pointers, ; since we may have just set up the previous block that way jz .last_block ; Set pointer to data block lea ax,[bx+4] ; Data past TFTP header mov [si+tftp_dataptr],ax add [si+tftp_filepos],ecx mov [si+tftp_bytesleft],cx cmp cx,[si+tftp_blksize] ; Is it a full block? jb .last_block ; If so, it's not EOF ; If we had the exact right number of bytes, always get ; one more packet to get the (zero-byte) EOF packet and ; close the socket. mov eax,[si+tftp_filepos] cmp [si+tftp_filesize],eax je .packet_loop ret.last_block: ; Last block - ACK packet immediately mov ax,[fs:bx+2] call ack_packet ; Make sure we know we are at end of file mov eax,[si+tftp_filepos] mov [si+tftp_filesize],eax ret;; ack_packet:;; Send ACK packet. This is a common operation and so is worth canning.;; Entry:; SI = TFTP block; AX = Packet # to ack (network byte order); Exit:; ZF = 0 -> Error; All registers preserved;; This function uses the pxe_udp_write_pkt but not the packet_buf.;ack_packet: pushad mov [ack_packet_buf+2],ax ; Packet number to ack mov ax,[si] mov [pxe_udp_write_pkt.lport],ax mov ax,[si+tftp_remoteport] mov [pxe_udp_write_pkt.rport],ax mov eax,[si+tftp_remoteip] mov [pxe_udp_write_pkt.sip],eax xor eax,[MyIP] and eax,[Netmask] jz .nogw mov eax,[Gateway].nogw: mov [pxe_udp_write_pkt.gip],eax mov [pxe_udp_write_pkt.buffer],word ack_packet_buf mov [pxe_udp_write_pkt.buffersize], word 4 mov di,pxe_udp_write_pkt mov bx,PXENV_UDP_WRITE call pxenv cmp ax,byte 0 ; ZF = 1 if write OK popad ret;; unload_pxe:;; This function unloads the PXE and UNDI stacks and unclaims; the memory.;unload_pxe: test byte [KeepPXE],01h ; Should we keep PXE around? jnz reset_pxe push ds push es mov ax,cs mov ds,ax mov es,ax mov si,new_api_unload cmp byte [APIVer+1],2 ; Major API version >= 2? jae .new_api mov si,old_api_unload.new_api: .call_loop: xor ax,ax lodsb and ax,ax jz .call_done xchg bx,ax mov di,pxe_unload_stack_pkt push di xor ax,ax mov cx,pxe_unload_stack_pkt_len >> 1 rep stosw pop di call pxenv jc .cant_free mov ax,word [pxe_unload_stack_pkt.status] cmp ax,PXENV_STATUS_SUCCESS jne .cant_free jmp .call_loop.call_done:%if USE_PXE_PROVIDED_STACK ; We need to switch to our local stack here... pusha pushf push gs mov si,sp mov ax,ss mov gs,ax sub ax,[BaseStack+4] ; Are we using the base stack je .is_base_stack ; (as opposed to the COMBOOT stack)? lgs si,[SavedSSSP] ; COMBOOT stack.is_base_stack: mov cx,[InitStack] mov di,StackBuf mov [BaseStack],di mov [BaseStack+4],es sub cx,si sub di,cx mov [SavedSSSP],di ; New SP mov [SavedSSSP+2],es gs rep movsb and ax,ax ; Remember which stack jne .combootstack ; Update the base stack pointer since it's in use lss sp,[SavedSSSP] .combootstack: pop gs popf popa%endif mov bx,0FF00h mov dx,[RealBaseMem] cmp dx,[BIOS_fbm] ; Sanity check jna .cant_free inc bx ; Check that PXE actually unhooked the INT 1Ah chain movzx eax,word [4*0x1a] movzx ecx,word [4*0x1a+2] shl ecx,4 add eax,ecx shr eax,10 cmp ax,dx ; Not in range jae .ok cmp ax,[BIOS_fbm] jae .cant_free ; inc bx.ok: mov [BIOS_fbm],dx.pop_ret: pop es pop ds ret .cant_free: mov si,cant_free_msg call writestr push ax xchg bx,ax call writehex4 mov al,'-' call writechr pop ax call writehex4 mov al,'-' call writechr mov eax,[4*0x1a] call writehex8 call crlf jmp .pop_ret ; We want to keep PXE around, but still we should reset ; it to the standard bootup configurationreset_pxe: push es push cs pop es mov bx,PXENV_UDP_CLOSE mov di,pxe_udp_close_pkt call pxenv pop es ret;; gendotquad;; Take an IP address (in network byte order) in EAX and; output a dotted quad string to ES:DI.; DI points to terminal null at end of string on exit.;; CX is destroyed.;gendotquad: push eax mov cx,4.genchar: push eax cmp al,10 ; < 10? jb .lt10 ; If so, skip first 2 digits cmp al,100 ; < 100 jb .lt100 ; If so, skip first digit aam 100 ; Now AH = 100-digit; AL = remainder add ah,'0' mov [es:di],ah inc di.lt100: aam 10 ; Now AH = 10-digit; AL = remainder add ah,'0' mov [es:di],ah inc di.lt10: add al,'0' stosb mov al,'.' stosb pop eax ror eax,8 ; Move next char into LSB loop .genchar dec di mov [es:di], byte 0 pop eax ret;; parse_dhcp;; Parse a DHCP packet. This includes dealing with "overloaded"; option fields (see RFC 2132, section 9.3);; This should fill in the following global variables, if the; information is present:;; MyIP - client IP address; ServerIP - boot server IP address; Netmask - network mask; Gateway - default gateway router IP; UseGW - zero if ServerIP local, otherwise Gateway; BootFile - boot file name;; This assumes the DHCP packet is in "trackbuf" and the length; of the packet in in CX on entry.;parse_dhcp: mov byte [OverLoad],0 ; Assume no overload mov eax, [trackbuf+bootp.yip] and eax, eax jz .noyip cmp al,224 ; Class D or higher -> bad jae .noyip mov [MyIP], eax.noyip: mov eax, [trackbuf+bootp.sip] and eax, eax jz .nosip cmp al,224 ; Class D or higher -> bad jae .nosip mov [ServerIP], eax.nosip: sub cx, bootp.options jbe .nooptions mov si, trackbuf+bootp.option_magic lodsd cmp eax, BOOTP_OPTION_MAGIC jne .nooptions call parse_dhcp_options.nooptions: mov si, trackbuf+bootp.bootfile test byte [OverLoad],1 jz .nofileoverload mov cx,128 call parse_dhcp_options jmp short .parsed_file.nofileoverload: cmp byte [si], 0 jz .parsed_file ; No bootfile name mov di,BootFile mov cx,32 rep movsd xor al,al stosb ; Null-terminate.parsed_file: mov si, trackbuf+bootp.sname
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -