📄 pxelinux.asm
字号:
jnz .get_opt_name ; Not end of packet ; ZF == 1 ; Success, done!.done_pkt: pop si ; Junk pop si ; We want the packet ptr in SI mov eax,[si+tftp_filesize] cmp eax,-1 jz .no_tsize mov edx,eax shr edx,16 ; DX:AX == EAX and eax,eax ; Set ZF depending on file size pop bp ; Junk pop bp ; Junk (retry counter) jz .error_si ; ZF = 1 need to free the socket.ret: pop bp pop cx pop bx pop es ret.no_tsize:.err_reply: ; Option negotiation error. Send ERROR reply. ; ServerIP and gateway are already programmed in mov si,[bp-6] mov ax,[si+tftp_remoteport] mov word [pxe_udp_write_pkt.rport],ax mov word [pxe_udp_write_pkt.buffer],tftp_opt_err mov word [pxe_udp_write_pkt.buffersize],tftp_opt_err_len mov di,pxe_udp_write_pkt mov bx,PXENV_UDP_WRITE call pxenv ; Write an error message and explode mov si,err_oldtftp call writestr jmp kaboom.bailnow: mov word [bp-2],1 ; Immediate error - no retry.failure: pop bx ; Junk pop bx pop si pop ax dec ax ; Retry counter jnz .sendreq ; Try again.error: mov si,bx ; Socket pointer.error_si: ; Socket pointer already in SI call free_socket ; ZF <- 1, SI <- 0 jmp .ret;; allocate_socket: Allocate a local UDP port structure;; If successful:; ZF set; BX = socket pointer; If unsuccessful:; ZF clear;allocate_socket: push cx mov bx,Files mov cx,MAX_OPEN.check: cmp word [bx], byte 0 je .found add bx,open_file_t_size loop .check xor cx,cx ; ZF = 1 pop cx ret ; Allocate a socket number. Socket numbers are made ; guaranteed unique by including the socket slot number ; (inverted, because we use the loop counter cx); add a ; counter value to keep the numbers from being likely to ; get immediately reused. ; ; The NextSocket variable also contains the top two bits ; set. This generates a value in the range 49152 to ; 57343..found: dec cx push ax mov ax,[NextSocket] inc ax and ax,((1 << (13-MAX_OPEN_LG2))-1) | 0xC000 mov [NextSocket],ax shl cx,13-MAX_OPEN_LG2 add cx,ax ; ZF = 0 xchg ch,cl ; Convert to network byte order mov [bx],cx ; Socket in use pop ax pop cx ret;; Free socket: socket in SI; return SI = 0, ZF = 1 for convenience;free_socket: push es pusha xor ax,ax mov es,ax mov di,si mov cx,tftp_pktbuf >> 1 ; tftp_pktbuf is not cleared rep stosw popa pop es xor si,si ret;; parse_dotquad:; Read a dot-quad pathname in DS:SI and output an IP; address in EAX, with SI pointing to the first; nonmatching character.;; Return CF=1 on error.;parse_dotquad: push cx mov cx,4 xor eax,eax.parseloop: mov ch,ah mov ah,al lodsb sub al,'0' jb .notnumeric cmp al,9 ja .notnumeric aad ; AL += 10 * AH; AH = 0; xchg ah,ch jmp .parseloop.notnumeric: cmp al,'.'-'0' pushf mov al,ah mov ah,ch xor ch,ch ror eax,8 popf jne .error loop .parseloop jmp .done.error: loop .realerror ; If CX := 1 then we're done clc jmp .done.realerror: stc.done: dec si ; CF unchanged! pop cx ret;; mangle_name: Mangle a filename pointed to by DS:SI into a buffer pointed; to by ES:DI; ends on encountering any whitespace.;; This verifies that a filename is < FILENAME_MAX characters; and doesn't contain whitespace, and zero-pads the output buffer,; so "repe cmpsb" can do a compare.;; The first four bytes of the manged name is the IP address of; the download host.;mangle_name: push si mov eax,[ServerIP] cmp byte [si],0 je .noip ; Null filename?!?! cmp word [si],'::' ; Leading ::? je .gotprefix.more: inc si cmp byte [si],0 je .noip cmp word [si],'::' jne .more ; We have a :: prefix of some sort, it could be either ; a DNS name or a dot-quad IP address. Try the dot-quad ; first....here: pop si push si call parse_dotquad jc .notdq cmp word [si],'::' je .gotprefix.notdq: pop si push si call dns_resolv cmp word [si],'::' jne .noip and eax,eax jnz .gotprefix.noip: pop si xor eax,eax jmp .prefix_done.gotprefix: pop cx ; Adjust stack inc si ; Skip double colon inc si.prefix_done: stosd ; Save IP address prefix mov cx,FILENAME_MAX-5.mn_loop: lodsb cmp al,' ' ; If control or space, end jna .mn_end stosb loop .mn_loop.mn_end: inc cx ; At least one null byte xor ax,ax ; Zero-fill name rep stosb ; Doesn't do anything if CX=0 ret ; Done;; unmangle_name: Does the opposite of mangle_name; converts a DOS-mangled; filename to the conventional representation. This is needed; for the BOOT_IMAGE= parameter for the kernel.; NOTE: A 13-byte buffer is mandatory, even if the string is; known to be shorter.;; DS:SI -> input mangled file name; ES:DI -> output buffer;; On return, DI points to the first byte after the output name,; which is set to a null byte.;unmangle_name: push eax lodsd and eax,eax jz .noip call gendotquad mov ax,'::' stosw.noip: call strcpy dec di ; Point to final null byte pop eax 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. In particular,; some PXE stacks seem to not like being invoked from anything but; the initial stack, so humour it.;pxenv:%if USE_PXE_PROVIDED_STACK == 0 mov [cs:PXEStack],sp mov [cs:PXEStack+2],ss lss sp,[cs:InitStack]%endif.jump: call 0:pxe_thunk ; Default to calling the thunk%if USE_PXE_PROVIDED_STACK == 0 lss sp,[cs:PXEStack]%endif 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:;; This isn't necessary anymore; we can use the memory area previously; used by the PXE stack indefinitely, and the chainload code sets up; a new stack independently. Leave the source code in here for now,; but expect to rip it out soonish.;%if 0 ; 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]
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -