Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
261 hidnplayr 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2
;;
3
;;  ARP.INC
4
;;
5
;;  Address Resolution Protocol
6
;;
7
;;  Last revision: 10.11.2006
8
;;
9
;;  This file contains the following:
10
;;   arp_table_manager - Manages an ARPTable
11
;;   arp_request - Sends an ARP request on the ethernet
12
;;   arp_handler - Called when an ARP packet is received
13
;;
14
;;  Changes history:
15
;;   22.09.2003 - [Mike Hibbett] : mikeh@oceanfree.net
16
;;   11.11.2006 - [Johnny_B] and [smb]
17
;;
18
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
19
 
20
ARP_NO_ENTRY                equ  0
21
ARP_VALID_MAPPING           equ  1
22
ARP_AWAITING_RESPONSE       equ  2
23
ARP_RESPONSE_TIMEOUT        equ  3
24
 
25
struc ARP_ENTRY     ;=14 bytes
26
{  .IP       dd  ?  ;+00
27
   .MAC      dp  ?  ;+04
28
   .Status   dw  ?  ;+10
29
   .TTL      dw  ?  ;+12 : ( in seconds )
30
}
31
 
32
virtual at 0
33
  ARP_ENTRY ARP_ENTRY
34
end virtual
35
 
36
; The TTL field is decremented every second, and is deleted when it
37
; reaches 0. It is refreshed every time a packet is received
38
; If the TTL field is 0xFFFF it is a static entry and is never deleted
39
; The status field can be the following values:
40
; 0x0000  entry not used
41
; 0x0001  entry holds a valid mapping
42
; 0x0002  entry contains an IP address, awaiting ARP response
43
; 0x0003  No response received to ARP request.
44
; The last status value is provided to allow the network layer to delete
45
; a packet that is queued awaiting an ARP response
46
 
47
 
48
; The follow is the ARP Table.
49
; This table must be manually updated and the kernel recompilied if
50
; changes are made to it.
51
; Empty entries are filled with zeros
52
 
53
ARP_ENTRY_SIZE              equ     14          ; Number of bytes per entry
54
ARP_TABLE_SIZE              equ     20          ; Size of table
55
ARP_TABLE_ENTRIES           equ     0           ; Number of static entries in the table
56
 
57
;TO ADD A STATIC ENTRY, DONT FORGET, PUT "ARPTable" from "uglobal" to "iglobal"!!!
58
;AND ALSO - IP and MAC have net byte-order, BUT STATUS AND TTL HAVE A MIRROR BYTE-ORDER!!!
59
uglobal
60
  ARPTable:
61
;example, static entry ->  db  11,22,33,44, 0x11,0x22,0x33,0x44,0x55,0x66, 0x01,0x00, 0xFF,0xFF
62
  times ( ARP_TABLE_SIZE - ARP_TABLE_ENTRIES ) * ARP_ENTRY_SIZE  db 0
63
endg
64
 
65
iglobal
66
  NumARP:        dd    ARP_TABLE_ENTRIES
67
  ARPTable_ptr   dd    ARPTable   ;pointer to ARPTable
68
endg
69
 
70
ARP_REQ_OPCODE              equ     0x0100  ;request
71
ARP_REP_OPCODE              equ     0x0200  ;reply
72
 
73
struc ARP_PACKET
74
{  .HardwareType dw   ?  ;+00
75
   .ProtocolType dw   ?  ;+02
76
   .HardwareSize db   ?  ;+04
77
   .ProtocolSize db   ?  ;+05
78
   .Opcode       dw   ?  ;+06
79
   .SenderMAC    dp   ?  ;+08
80
   .SenderIP     dd   ?  ;+14
81
   .TargetMAC    dp   ?  ;+18
82
   .TargetIP     dd   ?  ;+24
83
}
84
 
85
virtual at 0
86
  ARP_PACKET ARP_PACKET
87
end virtual
88
 
89
 
90
 
91
;***************************************************************************
92
;   Function
93
;      arp_table_manager  [by Johnny_B]
94
;
95
;   Description
96
;     Does a most required operations with ARP-table
97
;  IN:
98
;   Operation: see Opcode's constants below
99
;       Index: Index of entry in the ARP-table
100
;       Extra: Extra parameter for some Opcodes
101
;  OUT:
102
;   EAX = Returned value depends on opcodes, more detailed see below
103
;
104
;***************************************************************************
105
;Opcode's constants
106
ARP_TABLE_ADD                 equ  1
107
ARP_TABLE_DEL                 equ  2
108
ARP_TABLE_GET                 equ  3
109
ARP_TABLE_GET_ENTRIES_NUMBER  equ  4
110
ARP_TABLE_IP_TO_MAC           equ  5
111
ARP_TABLE_TIMER               equ  6
112
 
113
;Index's constants
114
EXTRA_IS_ARP_PACKET_PTR  equ  0   ;if Extra contain pointer to ARP_PACKET
115
EXTRA_IS_ARP_ENTRY_PTR   equ  -1  ;if Extra contain pointer to ARP_ENTRY
116
 
117
align 4
118
proc arp_table_manager stdcall uses ebx esi edi ecx edx,\
119
    Opcode:DWORD,Index:DWORD,Extra:DWORD
120
 
121
    mov     ebx, dword[ARPTable_ptr]   ;ARPTable base
122
    mov     ecx, dword[NumARP]         ;ARP-entries counter
123
 
124
    mov     eax, dword[Opcode]
125
    cmp     eax, ARP_TABLE_TIMER
126
    je      .timer
127
    cmp     eax, ARP_TABLE_ADD
128
    je      .add
129
    cmp     eax, ARP_TABLE_DEL
130
    je      .del
131
    cmp     eax, ARP_TABLE_GET
132
    je      .get
133
    cmp     eax, ARP_TABLE_IP_TO_MAC
134
    je      .ip_to_mac
135
    cmp     eax, ARP_TABLE_GET_ENTRIES_NUMBER
136
    je      .get_entries_number
137
    jmp     .exit     ;if unknown opcode
138
 
139
 
140
;;BEGIN TIMER
141
;;Description: it must be callback every second. It is responsible for removing expired routes.
142
;;IN:   Operation: ARP_TABLE_TIMER
143
;;      Index: must be zero
144
;;      Extra: must be zero
145
;;OUT:
146
;;  EAX=not defined
147
;;
148
.timer:
149
    test    ecx, ecx
150
    jz      .exit    ;if NumARP=0 nothing to do
151
    sub     ecx, ARP_TABLE_ENTRIES  ;ecx=dynamic entries number
152
    jz      .exit    ;if NumARP=number of static entries then exit
153
 
154
    add     ebx, ARP_TABLE_ENTRIES*ARP_ENTRY_SIZE  ;ebx=dynamic entries base
155
 
156
  .timer_loop:
157
    movsx   esi, word [ebx + ARP_ENTRY.TTL]
158
    cmp     esi, 0xFFFFFFFF
159
    je      .timer_loop_end  ;if TTL==0xFFFF then it's static entry
160
 
161
    test    esi, esi
162
    jnz     .timer_loop_end_with_dec  ;if TTL!=0
163
 
164
    ; Ok, TTL is 0
165
    ;if Status==AWAITING_RESPONSE and TTL==0
166
    ;then we have to change it to ARP_RESPONSE_TIMEOUT
167
    cmp     word [ebx + ARP_ENTRY.Status], ARP_AWAITING_RESPONSE
168
    jne     @f
169
 
170
    mov     word [ebx + ARP_ENTRY.Status], ARP_RESPONSE_TIMEOUT
171
    mov     word [ebx + ARP_ENTRY.TTL], word 0x000A   ;10 sec
172
    jmp     .timer_loop_end
173
 
174
  @@:
175
    ;if TTL==0 and Status==VALID_MAPPING, we have to delete it
176
    ;if TTL==0 and Status==RESPONSE_TIMEOUT, delete too
177
    mov     esi, dword[NumARP]
178
    sub     esi, ecx          ;esi=index of entry, will be deleted
179
    stdcall arp_table_manager,ARP_TABLE_DEL,esi,0 ;opcode,index,extra
180
    jmp     .timer_loop_end
181
 
182
 
183
  .timer_loop_end_with_dec:
184
    dec     word [ebx + ARP_ENTRY.TTL]  ;decrease TTL
185
  .timer_loop_end:
186
    add     ebx, ARP_ENTRY_SIZE
187
    loop    .timer_loop
188
 
189
    jmp     .exit
190
;;END TIMER
191
 
192
;;BEGIN ADD
193
;;Description: it adds an entry in the table. If ARP-table already
194
;;             contains same IP, it will be updated.
195
;;IN:   Operation: ARP_TABLE_ADD
196
;;      Index: specifies what contains Extra-parameter
197
;;      Extra: if Index==EXTRA_IS_ARP_PACKET_PTR,
198
;;             then Extra contains pointer to ARP_PACKET,
199
;;             otherwise Extra contains pointer to ARP_ENTRY
200
;;OUT:
201
;;  EAX=index of entry, that has been added
202
;;
203
.add:
204
 
205
    sub     esp, ARP_ENTRY_SIZE   ;Allocate ARP_ENTRY_SIZE byte in stack
206
 
207
    mov     esi, [Extra]   ;pointer
208
    mov     edi, [Index]   ;opcode
209
 
210
    cmp     edi, EXTRA_IS_ARP_PACKET_PTR
211
    je      .arp_packet_to_entry ;if Extra contain ptr to ARP_PACKET and we have to form arp-entry
212
                                 ;else it contain ptr to arp-entry
213
 
214
    cld
215
          ; esi already has been loaded
216
    mov     edi, esp      ;ebx + eax=ARPTable_base + ARP-entry_base(where we will add)
217
    mov     ecx,ARP_ENTRY_SIZE/2  ;ARP_ENTRY_SIZE must be even number!!!
218
    rep     movsw    ;copy
219
    jmp     .search
220
 
221
  .arp_packet_to_entry:
222
    mov     edx, dword[esi + ARP_PACKET.SenderIP] ;esi=base of ARP_PACKET
223
    mov     [esp + ARP_ENTRY.IP], edx
224
 
225
    cld
226
    lea     esi, [esi + ARP_PACKET.SenderMAC]
227
    lea     edi, [esp + ARP_ENTRY.MAC]
228
    movsd
229
    movsw
230
    mov     word[esp + ARP_ENTRY.Status], ARP_VALID_MAPPING  ; specify the type - a valid entry
231
    mov     word[esp + ARP_ENTRY.TTL], 0x0E10    ; = 1 hour
232
 
233
  .search:
234
    mov     edx, dword[esp + ARP_ENTRY.IP]  ;edx=IP-address, which we'll search
235
    mov     ecx, dword[NumARP]              ;ecx=ARP-entries counter
236
    jecxz   .add_to_end                     ;if ARP-entries number == 0
237
    imul    eax, ecx, ARP_ENTRY_SIZE        ;eax=current table size(in bytes)
238
  @@:
239
    sub     eax, ARP_ENTRY_SIZE
240
    cmp     dword[ebx + eax + ARP_ENTRY.IP], edx
241
    loopnz  @b
242
    jz      .replace       ; found, replace existing entry, ptr to it is in eax
243
 
244
  .add_to_end:
245
    ;else add to end
246
    or      eax,-1    ;set eax=0xFFFFFFFF if adding is impossible
247
    mov     ecx, dword[NumARP]
248
    cmp     ecx, ARP_TABLE_SIZE
249
    je      .add_exit   ;if arp-entries number is equal to arp-table maxsize
250
 
251
    imul    eax, dword[NumARP], ARP_ENTRY_SIZE ;eax=ptr to end of ARPTable
252
    inc     dword [NumARP]    ;increase ARP-entries counter
253
 
254
  .replace:
255
    cld
256
    mov     esi, esp              ;esp=base of ARP-entry, that will be added
257
    lea     edi, [ebx + eax]      ;ebx + eax=ARPTable_base + ARP-entry_base(where we will add)
258
    mov     ecx,ARP_ENTRY_SIZE/2  ;ARP_ENTRY_SIZE must be even number!!!
259
    rep     movsw
260
 
261
    mov     ecx, ARP_ENTRY_SIZE
262
    xor     edx, edx  ;"div" takes operand from EDX:EAX
263
    div     ecx       ;eax=index of entry, which has been added
264
 
265
.add_exit:
266
    add     esp, ARP_ENTRY_SIZE   ;free stack
267
    jmp     .exit
268
;;END ADD
269
 
270
;;BEGIN DEL
271
;;Description: it deletes an entry in the table.
272
;;IN:   Operation: ARP_TABLE_DEL
273
;;      Index: index of entry, that should be deleted
274
;;      Extra: must be zero
275
;;OUT:
276
;;  EAX=not defined
277
;;
278
.del:
279
    mov     esi, [Index]
280
    imul    esi, ARP_ENTRY_SIZE
281
 
282
    mov     ecx, (ARP_TABLE_SIZE - 1) * ARP_ENTRY_SIZE
283
    sub     ecx, esi
284
 
285
    lea     edi, [ebx + esi]            ;edi=ptr to entry that should be deleted
286
    lea     esi, [edi + ARP_ENTRY_SIZE] ;esi=ptr to next entry
287
 
288
    shr     ecx,1      ;ecx/2 => ARP_ENTRY_SIZE MUST BE EVEN NUMBER!
289
    cld
290
    rep     movsw
291
 
292
    dec     dword[NumARP] ;decrease arp-entries counter
293
    jmp     .exit
294
;;END DEL
295
 
296
;;BEGIN GET
297
;;Description: it reads an entry of table into buffer.
298
;;IN:   Operation: ARP_TABLE_GET
299
;;      Index: index of entry, that should be read
300
;;      Extra: pointer to buffer for reading(size must be equal to ARP_ENTRY_SIZE)
301
;;OUT:
302
;;  EAX=not defined
303
;;
304
.get:
305
    mov     esi, [Index]
306
    imul    esi, ARP_ENTRY_SIZE   ;esi=ptr to required ARP_ENTRY
307
    mov     edi, [Extra]          ;edi=buffer for reading
308
    mov     ecx, ARP_ENTRY_SIZE/2 ; must be even number!!!
309
    cld
310
    rep     movsw
311
    jmp     .exit
312
;;END GET
313
 
314
;;BEGIN IP_TO_MAC
315
;;Description: it gets an IP from Index, scans each entry in the table and writes
316
;;             MAC, that relates to specified IP, into buffer specified in Extra.
317
;;             And if it cannot find an IP-address in the table, it does an ARP-request of that.
318
;;IN:   Operation: ARP_TABLE_IP_TO_MAC
319
;;      Index: IP that should be transformed into MAC
320
;;      Extra: pointer to buffer where will be written the MAC-address.
321
;;OUT:
322
;;  EAX=ARP table entry status code.
323
;;      If EAX==ARP_NO_ENTRY, IP isn't found in the table and we have sent the request.
324
;;      If EAX==ARP_AWAITING_RESPONSE, we wait the response from remote system.
325
;;      If EAX==ARP_RESPONSE_TIMEOUT, remote system not responds too long.
326
;;      If EAX==ARP_VALID_MAPPING, all is ok, we've got a true MAC.
327
;;
328
;;  If MAC will equal to a zero, in the buffer. It means, that IP-address was not yet
329
;;  resolved, or that doesn't exist. I recommend you, to do at most 3-5 calls of this
330
;;  function with 1sec delay. sure, only if it not return a valid MAC after a first call.
331
;;
332
.ip_to_mac:
333
 
334
    xor     eax, eax
335
    mov     edi, dword[Extra]
336
    cld
337
    stosd
338
    stosw
339
 
340
    cmp     dword[NumARP], 0
341
    je      .ip_to_mac_send_request ;if ARP-table not contain an entries, we have to request IP.
342
                                    ;EAX will be containing a zero, it's equal to ARP_NO_ENTRY
343
 
344
    ; first, check destination IP to see if it is on 'this' network.
345
    ; The test is:
346
    ; if ( destIP & subnet_mask == stack_ip & subnet_mask )
347
    ;   destination is local
348
    ; else
349
    ;  destination is remote, so pass to gateway
350
 
351
    mov     eax, [Index]       ;eax=required IP
352
    mov     esi, eax
353
    and     esi, [subnet_mask]
354
    mov     ecx, [stack_ip]
355
    and     ecx, [subnet_mask]
356
    cmp     esi, ecx
357
    je      @f        ;if we and target IP are located in the same network
358
    mov     eax, [gateway_ip]
359
  @@:
360
 
361
    mov     ecx, dword[NumARP]
362
    imul    esi, ecx, ARP_ENTRY_SIZE  ;esi=current ARP-table size
363
 
364
  @@:
365
    sub     esi, ARP_ENTRY_SIZE
366
    cmp     [ebx + esi], eax         ; ebx=ARPTable base
367
    loopnz  @b                       ; Return back if non match
368
    jnz     .ip_to_mac_send_request  ; and request IP->MAC if none found in the table
369
 
370
    ; Return the entry status in eax
371
    movzx   eax, word[ebx + esi + ARP_ENTRY.Status]
372
 
373
    ; esi holds index
374
    cld
375
    lea     esi, [ebx + esi + ARP_ENTRY.MAC]
376
    mov     edi, [Extra]   ;edi=ptr to buffer for write MAC
377
    movsd
378
    movsw
379
    jmp     .exit
380
 
381
  .ip_to_mac_send_request:
382
    stdcall arp_request,[Index],stack_ip,node_addr  ;TargetIP,SenderIP_ptr,SenderMAC_ptr
383
    mov     eax, ARP_NO_ENTRY
384
    jmp     .exit
385
 
386
;;END IP_TO_MAC
387
 
388
;;BEGIN GET_ENTRIES_NUMBER
389
;;Description: returns an ARP-entries number in the ARPTable
390
;;IN:   Operation: ARP_TABLE_GET_ENTRIES_NUMBER
391
;;      Index: must be zero
392
;;      Extra: must be zero
393
;;OUT:
394
;;  EAX=ARP-entries number in the ARPTable
395
  .get_entries_number:
396
    mov     eax, dword[NumARP]
397
    jmp     .exit
398
;;END GET_ENTRIES_NUMBER
399
 
400
.exit:
401
    ret
402
endp
403
 
404
 
405
;***************************************************************************
406
;   Function
407
;      arp_handler
408
;
409
;   Description
410
;      Called when an ARP packet is received on the ethernet
411
;      Header + Data is in Ether_buffer[]
412
;       It looks to see if the packet is a request to resolve this Hosts
413
;       IP address. If it is, send the ARP reply packet.
414
;      This Hosts IP address is in dword [stack_ip]  ( in network format )
415
;       This Hosts MAC address is in node_addr[6]
416
;      All registers may be destroyed
417
;
418
;***************************************************************************
419
arp_handler:
420
    ; Is this a REQUEST?
421
    ; Is this a request for My Host IP
422
    ; Yes - So construct a response message.
423
    ; Send this message to the ethernet card for transmission
424
 
425
    stdcall arp_table_manager,ARP_TABLE_ADD,EXTRA_IS_ARP_PACKET_PTR,ETH_FRAME.Data + ARP_PACKET
426
 
427
    inc     dword[arp_rx_count] ;increase ARP-packets counter
428
 
429
    cmp     word[ETH_FRAME.Data + ARP_PACKET.Opcode], ARP_REQ_OPCODE  ; Is this a request packet?
430
    jne     .exit            ; No - so exit
431
 
432
    mov     eax, [stack_ip]
433
    cmp     eax, dword[ETH_FRAME.Data + ARP_PACKET.TargetIP]         ; Is it looking for my IP address?
434
    jne     .exit            ; No - so quit now
435
 
436
    ; OK, it is a request for my MAC address. Build the frame and send it
437
    ; We can reuse the packet.
438
 
439
    mov     word[ETH_FRAME.Data + ARP_PACKET.Opcode], ARP_REP_OPCODE
440
 
441
    cld
442
    mov     esi, ETH_FRAME.Data + ARP_PACKET.SenderMAC
443
    mov     edi, ETH_FRAME.Data + ARP_PACKET.TargetMAC
444
    movsd
445
    movsw
446
 
447
    mov     esi, ETH_FRAME.Data + ARP_PACKET.SenderIP
448
    mov     edi, ETH_FRAME.Data + ARP_PACKET.TargetIP
449
    movsd
450
 
451
    mov     esi, node_addr
452
    mov     edi, ETH_FRAME.Data + ARP_PACKET.SenderMAC
453
    movsd
454
    movsw
455
 
456
    mov     esi, stack_ip
457
    mov     edi, ETH_FRAME.Data + ARP_PACKET.SenderIP
458
    movsd
459
 
460
    ; Now, send it!
461
    mov     edi, ETH_FRAME.Data + ARP_PACKET.TargetMAC   ;ptr to destination MAC address
462
    mov     bx, ETHER_ARP               ;type of protocol
463
    mov     ecx, 28                     ;data size
464
    mov     esi, ETH_FRAME.Data + ARP_PACKET             ;ptr to data
465
    call    dword [drvr_transmit]       ;transmit packet
466
 
467
  .exit:
468
    ret
469
 
470
 
471
;***************************************************************************
472
;   Function
473
;      arp_request  [by Johnny_B]
474
;
475
;   Description
476
;      Sends an ARP request on the ethernet
477
;   IN:
478
;     TargetIP      : requested IP address
479
;     SenderIP_ptr  : POINTER to sender's IP address(our system's address)
480
;     SenderMAC_ptr : POINTER to sender's MAC address(our system's address)
481
;   OUT:
482
;     EAX=0 (if all is ok), otherwise EAX is not defined
483
;
484
;      EBX,ESI,EDI will be saved
485
;
486
;***************************************************************************
487
proc arp_request stdcall uses ebx esi edi,\
488
    TargetIP:DWORD, SenderIP_ptr:DWORD, SenderMAC_ptr:DWORD
489
 
490
    inc     dword[arp_tx_count]  ; increase counter
491
 
492
    sub     esp, 28  ; allocate memory for ARP_PACKET
493
 
494
    mov     word[esp + ARP_PACKET.HardwareType],0x0100 ;Ethernet
495
    mov     word[esp + ARP_PACKET.ProtocolType],0x0008 ;IP
496
    mov     byte[esp + ARP_PACKET.HardwareSize],0x06   ;MAC-addr length
497
    mov     byte[esp + ARP_PACKET.ProtocolSize],0x04   ;IP-addr length
498
    mov     word[esp + ARP_PACKET.Opcode],0x0100       ;Request
499
 
500
    cld
501
    mov     esi,[SenderMAC_ptr]
502
    lea     edi,[esp + ARP_PACKET.SenderMAC]       ;Our MAC-addr
503
    movsd
504
    movsw
505
 
506
    mov     esi,[SenderIP_ptr]
507
    lea     edi,[esp + ARP_PACKET.SenderIP]        ;Our IP-addr
508
    movsd
509
 
510
    xor     eax, eax
511
    lea     edi, [esp + ARP_PACKET.TargetMAC]      ;Required MAC-addr(zeroed)
512
    stosd
513
    stosw
514
 
515
    mov     esi, dword[TargetIP]
516
    mov     dword[esp + ARP_PACKET.TargetIP],esi   ;Required IP-addr(we get it as function parameter)
517
 
518
    ; Now, send it!
519
    mov     edi, broadcast_add     ; Pointer to 48 bit destination address
520
    mov     bx, ETHER_ARP          ; Type of packet
521
    mov     ecx, 28                ; size of packet
522
    lea     esi, [esp + ARP_PACKET]; pointer to packet data
523
    call    dword [drvr_transmit]  ; Call the drivers transmit function
524
 
525
    add     esp, 28  ; free memory, allocated before for ARP_PACKET
526
 
527
    ; Add an entry in the ARP table, awaiting response
528
    sub     esp, ARP_ENTRY_SIZE    ;allocate memory for ARP-entry
529
 
530
    mov     esi, dword[TargetIP]
531
    mov     dword[esp + ARP_ENTRY.IP],esi
532
 
533
    lea     edi, [esp + ARP_ENTRY.MAC]
534
    xor     eax, eax
535
    stosd
536
    stosw
537
 
538
    mov     word[esp + ARP_ENTRY.Status], ARP_AWAITING_RESPONSE
539
    mov     word[esp + ARP_ENTRY.TTL], 0x000A  ; 10 seconds
540
 
541
    stdcall arp_table_manager,ARP_TABLE_ADD,EXTRA_IS_ARP_ENTRY_PTR,esp
542
    add     esp, ARP_ENTRY_SIZE  ; free memory
543
 
544
.exit:
545
    ret
546
endp