COVERAGE SUMMARY
FILE SUMMARY
NameExecutedRoutines%ExecutedLines%Unexecuted
/home/matt/eu/rds/include/std/safe.e334082.50%30036282.87%62
ROUTINE SUMMARY
RoutineExecutedLinesUnexecuted
show_block()344379.07%9
info()060.00%6
memory_used()060.00%6
free_code()131776.47%4
safe_address()313588.57%4
dep_works()030.00%3
unregister_block()71070.00%3
allocations()020.00%2
check_all_blocks()101283.33%2
deallocate()202290.91%2
far_addr()020.00%2
low_machine_addr()020.00%2
mem_copy()5771.43%2
permits()020.00%2
call()6785.71%1
die()8988.89%1
mem_set()4580.00%1
peek()8988.89%1
peek2s()8988.89%1
peek2u()8988.89%1
peek4s()8988.89%1
peek4u()8988.89%1
peek_string()101190.91%1
peeks()8988.89%1
poke2()7887.50%1
poke4()7887.50%1
prepare_block()8988.89%1
bad_address()22100.00%0
bordered_address()77100.00%0
c_func()55100.00%0
c_proc()55100.00%0
does_not_permit()88100.00%0
ext_addr()22100.00%0
int_addr()22100.00%0
machine_addr()22100.00%0
natural()22100.00%0
poke()88100.00%0
positive_int()22100.00%0
register_block()88100.00%0
show_byte()1111100.00%0
LINE COVERAGE DETAIL
#Executed
1
-- (c) Copyright - See License.txt
2
--
3
-- Euphoria 4.0
4
-- Machine Level Programming (386/486/Pentium)
5
6
--****
7
-- === safe.e
8
--
9
-- This is a slower DEBUGGING VERSION of machine.e
10
--
11
-- How To Use This File:
12
--
13
-- 1. If your program doesn't already include machine.e add:
14
-- include std/machine.e
15
-- to your main .ex[w][u] file at the top.
16
--
17
-- 2. To turn debug version on, issue
18
--
19
-- with define SAFE
20
--
21
-- in your main program, before the statement including machine.e.
22
--
23
-- 3. If necessary, call register_block(address, length, memory_protection) to add additional
24
-- "external" blocks of memory to the safe_address_list. These are blocks
25
-- of memory that are safe to use but which you did not acquire
26
-- through Euphoria's allocate(), allocate_data(), allocate_code() or memory_protect(). Call
27
-- unregister_block(address) when you want to prevent further access to
28
-- an external block.
29
--
30
-- 4. Run your program. It might be 10x slower than normal but it's
31
-- worth it to catch a nasty bug.
32
--
33
-- 5. If a bug is caught, you will hear some "beep" sounds.
34
-- Press Enter to clear the screen and see the error message.
35
-- There will be a "divide by zero" traceback in ex.err
36
-- so you can find the statement that is making the illegal memory access.
37
--
38
-- 6. To switch between normal and debug versions, simply comment in or out the
39
-- "with define SAFE" directive. In means debugging and out means normal.
40
-- Alternatively, you can use -D SAFE as a switch on the command line (debug) or not (normal).
41
--
42
-- 7. The older method of switching files and renaming them //**no longer works**//. machine.e conditionally includes safe.e.
43
--
44
-- This file is equivalent to machine.e, but it overrides the built-in
45
-- routines:
46
-- poke, peek, poke4, peek4s, peek4u, call, mem_copy, and mem_set
47
-- and it provides alternate versions of:
48
-- allocate, free
49
--
50
-- Your program will only be allowed to read/write areas of memory
51
-- that it allocated (and hasn't freed), as well as areas in low memory
52
-- that you list below, or add dynamically via register_block().
53
54
namespace safe
55
include std/error.e
568
ifdef WINDOWS then
57
include std/win32/sounds.e
58
end ifdef
59
public include std/memconst.e
608
ifdef DATA_EXECUTE then
61
public include std/machine.e as machine
62
end ifdef
63
64
65
66
-- Some parameters you may wish to change:
67
68
--**
69
-- Define block checking policy.
70
--
71
-- Comments:
72
--
73
-- If this integer is 1, (the default), check all blocks for edge corruption after each
74
-- [[:call]](), [[:c_proc]]() or [[:c_func]]().
75
-- To save time, your program can turn off this checking by setting check_calls to 0.
76
778
public integer check_calls = 1
78
79
--**
80
-- Determine whether to flag accesses to remote memory areas.
81
--
82
-- Comments:
83
--
84
-- If this integer is 1 (the default under //WIN32//), only check for references to the
85
-- leader or trailer areas just outside each registered block, and don't complain about
86
-- addresses that are far out of bounds (it's probably a legitimate block from another source)
87
--
88
-- For a stronger check, set this to 0 if your program will never read/write an
89
-- unregistered block of memory.
90
--
91
-- On //WIN32// people often use unregistered blocks.
928
public integer edges_only = (platform()=2)
93
94
-- Constants that tell us what we are about to try to do: read, write or execute memory.
95
-- They should be distinct from PERM_EXEC, etc...
96
-- These are not permission constants.
97
98
-- Internal types for understanding more than type-checking:
99
100
-- internal address
101
-- addresses used for passing to and getting from low level machine_func calls and to a few
102
-- local only routines.
10346
10446
return 1
105
end type
106
107
-- external address
108
-- addresses used for passing to and getting from high level functions in machine.e and
109
-- public functions declared here.
11076
11176
return 1
112
end type
113
114
115
-- Include the starting address and length of any
116
-- acceptable areas of memory for peek/poke here.
117
-- Set allocation number to 0.
118
-- This symbol is *only* available from std/machine.e when SAFE is defined.
1198
public sequence safe_address_list = {}
120
1218
enum BLOCK_ADDRESS, BLOCK_LENGTH, ALLOC_NUMBER, BLOCK_PROT
122
123
with type_check
124
1258
constant OK = 1, BAD = 0
1268
constant M_ALLOC = 16,
1278
M_FREE = 17,
1288
M_SLEEP = 64
129
1308
puts(1, "\n\t\tUsing Debug Version of machine.e\n")
131
-- machine_proc(M_SLEEP, 3)
132
133
-- biggest address on a 32-bit machine
1348
constant MAX_ADDR = power(2, 32)-1
135
136
-- biggest address accessible to 16-bit real mode
1378
constant LOW_ADDR = power(2, 20)-1
138
139197
140197
return x >= 1
141
end type
142
143109
144109
return x >= 0
145
end type
146
147263
148
-- a 32-bit non-null machine address
149263
return a > 0 and a <= MAX_ADDR and floor(a) = a
150
end type
151
1520
153
-- protected mode far address {seg, offset}
1540
return length(a) = 2 and integer(a[1]) and machine_addr(a[2])
155
end type
156
1570
158
-- a legal low machine address
1590
return a > 0 and a <= LOW_ADDR and floor(a) = a
160
end type
161
1628
export constant BORDER_SPACE = 40
1638
export constant leader = repeat('@', BORDER_SPACE)
1648
export constant trailer = repeat('%', BORDER_SPACE)
165
16620
167
sequence l
16820
for i = 1 to length(safe_address_list) do
16933
if safe_address_list[i][BLOCK_ADDRESS] = addr then
17018
l = eu:peek( {addr - BORDER_SPACE, BORDER_SPACE} )
17118
return equal(l, leader)
172
end if
17315
end for
1742
return 0
175
end type
176
177
-- Return true if /action/ is not allowed when memory has /protection/
17856
17956
if action = A_READ and not test_read( protection ) then
1801
return 1
18155
elsif action = A_WRITE and not test_write( protection ) then
1824
return 1
18351
elsif action = A_EXECUTE and not test_exec( protection ) then
1844
return 1
185
else
18647
return 0
187
end if
188
end function
189
190
-- Return true if /action/ is allowed when memory has /protection/
1910
1920
return not does_not_permit(protection,action)
193
end function
194
19567
196
-- is it ok to read/write all addresses from start to start+len-1?
197
-- Note: This routine is available from std/machine.e *only* when SAFE
198
-- is defined.
199
atom block_start, block_upper, upper
200
sequence block
201
20267
if len = 0 then
2031
return OK
204
end if
205
20666
upper = start + len
207
-- search the list of safe memory blocks:
20866
for i = 1 to length(safe_address_list) do
209372
block = safe_address_list[i]
210372
block_start = block[BLOCK_ADDRESS]
211372
if edges_only then
212
-- addresses are considered safe as long as
213
-- they aren't in any block's border zone and
214
-- if they are in a block, the action is permitted
215
-- for that block's protection
216331
if start <= 3 then
2171
return BAD -- null pointer (or very small address)
218
end if
219330
if block[ALLOC_NUMBER] >= 1 then
220
-- an allocated block with a border area
221330
block_upper = block_start + block[BLOCK_LENGTH]
222330
if (start >= block_start - BORDER_SPACE and
223
start < block_start) or
224
(start >= block_upper and
225
start < block_upper + BORDER_SPACE) then
2261
return BAD
227
228329
elsif (upper > block_start - BORDER_SPACE and
229
upper <= block_start) or
230
(upper > block_upper and
231
upper < block_upper + BORDER_SPACE) then
2321
return BAD
233
234328
elsif start < block_start - BORDER_SPACE and
235
upper > block_upper + BORDER_SPACE then
2361
return BAD
237
end if
238
end if
239327
if ( (block_start <= start and start <= block_upper) or
240
(block_start <= upper and upper <= block_upper) )
241
and
242
does_not_permit( block[BLOCK_PROT], action )
243
then
2441
return BAD
245
end if
246
else
247
-- addresses are considered safe as long as
248
-- they are inside an allocated or registered block
249
-- whose protection permits the current action.
25041
if start >= block_start then
25141
block_upper = block_start + block[BLOCK_LENGTH]
25241
if upper <= block_upper then
25317
if i > 1 then
254
-- move block i to the top and move 1..i-1 down
2550
if i = 2 then
256
-- common case, subscript is faster than slice:
2570
safe_address_list[2] = safe_address_list[1]
258
else
2590
safe_address_list[2..i] = safe_address_list[1..i-1]
260
end if
2610
safe_address_list[1] = block
262
end if
26317
if does_not_permit( block[BLOCK_PROT], action ) then
2648
return BAD
265
else
2669
return OK
267
end if
268
end if
269
end if
270
end if
271350
end for
27244
if edges_only then
27340
return OK -- not found in any border zone
274
else
2754
return BAD -- not found in any safe block
276
end if
277
end function
278
2796
280
-- Terminate with a message.
281
-- makes warning beeps first so you can see what's happening on the screen
2826
for i = 1 to 3 do
28318
ifdef WINDOWS then
284
sound()
285
end ifdef
28618
machine_func(M_SLEEP,0.1)
28718
ifdef WINDOWS then
288
sound(0)
289
end ifdef
29018
machine_func(M_SLEEP,0.1)
29118
end for
2926
crash(msg)
2930
end procedure
294
2951
296
-- show address in decimal and hex
2971
return sprintf(" ADDRESS!!!! %d (#%08x)", {a, a})
298
end function
299
300
without warning &= (override)
301
3025
303
-- safe version of peek
304
integer len
305
atom a
306
3075
if atom(x) then
3081
len = 1
3091
a = x
310
else
3114
len = x[2]
3124
a = x[1]
313
end if
3145
if safe_address(a, len, A_READ) then
3155
return eu:peek(x)
316
else
3170
die("BAD PEEK" & bad_address(a))
318
end if
319
end function
320
3212
322
-- safe version of peeks
323
integer len
324
atom a
325
3262
if atom(x) then
3271
len = 1
3281
a = x
329
else
3301
len = x[2]
3311
a = x[1]
332
end if
3332
if safe_address(a, len, A_READ) then
3342
return eu:peeks(x)
335
else
3360
die("BAD PEEK" & bad_address(a))
337
end if
338
end function
339
3402
341
-- safe version of peek
342
integer len
343
atom a
344
3452
if atom(x) then
3461
len = 2
3471
a = x
348
else
3491
len = x[2] * 2
3501
a = x[1]
351
end if
3522
if safe_address(a, len, A_READ) then
3532
return eu:peek2u(x)
354
else
3550
die("BAD PEEK2U" & bad_address(a))
356
end if
357
end function
358
3592
360
-- safe version of peek
361
integer len
362
atom a
363
3642
if atom(x) then
3651
len = 2
3661
a = x
367
else
3681
len = x[2] * 2
3691
a = x[1]
370
end if
3712
if safe_address(a, len, A_READ) then
3722
return eu:peek2s(x)
373
else
3740
die("BAD PEEK2S" & bad_address(a))
375
end if
376
end function
377
3782
379
-- safe version of peek4s
380
integer len
381
atom a
382
3832
if atom(x) then
3841
len = 4
3851
a = x
386
else
3871
len = x[2]*4
3881
a = x[1]
389
end if
3902
if safe_address(a, len, A_READ) then
3912
return eu:peek4s(x)
392
else
3930
die("BAD PEEK4S" & bad_address(a))
394
end if
395
end function
396
3972
398
-- safe version of peek4u
399
integer len
400
atom a
401
4022
if atom(x) then
4031
len = 4
4041
a = x
405
else
4061
len = x[2]*4
4071
a = x[1]
408
end if
4092
if safe_address(a, len, A_READ) then
4102
return eu:peek4u(x)
411
else
4120
die("BAD PEEK4U" & bad_address(a))
413
end if
414
end function
415
416
4171
418
-- safe version of peek_string
419
integer len
4201
atom a = x
421
4221
len = 1
4231
while 1 do
4247
if safe_address( a, len, A_READ ) then
4257
if not eu:peek( a + len - 1 ) then
4261
exit
427
else
4286
len += 1
429
end if
430
else
4310
die("BAD PEEK_STRING" & bad_address(a))
432
end if
4336
end while
4341
return eu:peek_string(x)
435
end function
436
437
4387
439
-- safe version of poke
440
integer len
441
4427
if atom(v) then
4432
len = 1
444
else
4455
len = length(v)
446
end if
4477
if safe_address(a, len, A_WRITE) then
4486
eu:poke(a, v)
449
else
4501
die("BAD POKE" & bad_address(a))
451
end if
4526
end procedure
453
4544
455
-- safe version of poke2
456
integer len
457
4584
if atom(v) then
4592
len = 2
460
else
4612
len = length(v) * 2
462
end if
4634
if safe_address(a, len, A_WRITE) then
4644
eu:poke2(a, v)
465
else
4660
die("BAD POKE" & bad_address(a))
467
end if
4684
end procedure
469
4704
471
-- safe version of poke4
472
integer len
473
4744
if atom(v) then
4752
len = 4
476
else
4772
len = length(v)*4
478
end if
4794
if safe_address(a, len, A_WRITE) then
4804
eu:poke4(a, v)
481
else
4820
die("BAD POKE4" & bad_address(a))
483
end if
4844
end procedure
485
4861
487
-- safe mem_copy
4881
if not safe_address(target, len, A_WRITE) then
4890
die("BAD MEM_COPY TARGET" & bad_address(target))
4901
elsif not safe_address(source, len, A_READ) then
4910
die("BAD MEM_COPY SOURCE" & bad_address(source))
492
else
4931
eu:mem_copy(target, source, len)
494
end if
4951
end procedure
496
4971
498
-- safe mem_set
4991
if safe_address(target, len, A_WRITE) then
5001
eu:mem_set(target, value, len)
501
else
5020
die("BAD MEM_SET" & bad_address(target))
503
end if
5041
end procedure
505
506
atom allocation_num
5078
allocation_num = 0
508
50949
510
-- display byte at memory location m
511
integer c
512
51349
c = eu:peek(m)
51449
if c <= 9 then
5152
printf(1, "%d", c)
51647
elsif c < 32 or c > 127 then
5175
printf(1, "%d #%02x", {c, c})
518
else
51942
if c = leader[1] or c = trailer[1] then
52039
printf(1, "%s", c)
521
else
5223
printf(1, "%d #%02x '%s'", {c, c, c})
523
end if
524
end if
52549
puts(1, ", ")
52649
end procedure
527
5282
529
-- display a corrupted block and die
530
integer len, id, bad, p
531
atom start
532
5332
start = block_info[1]
5342
len = block_info[2]
5352
id = block_info[3]
5362
printf(1, "BLOCK# %d, START: #%x, SIZE %d\n", {id, start, len})
537
-- check pre-block
5382
bad = 0
5392
for i = start-BORDER_SPACE to start-1 do
54080
p = eu:peek(i)
54180
if p != leader[1] or bad then
5421
bad += 1
5431
if bad = 1 then
5441
puts(1, "DATA WAS STORED ILLEGALLY, JUST BEFORE THIS BLOCK:\n")
5451
puts(1, "(" & leader[1] & " characters are OK)\n")
5461
printf(1, "#%x: ", i)
547
end if
5481
show_byte(i)
549
end if
55080
end for
5512
puts(1, "\nDATA WITHIN THE BLOCK:\n")
5522
printf(1, "#%x: ", start)
5532
if len <= 30 then
554
-- show whole block
5552
for i = start to start+len-1 do
5568
show_byte(i)
5578
end for
558
else
559
-- first part of block
5600
for i = start to start+14 do
5610
show_byte(i)
5620
end for
563
-- last part of block
5640
puts(1, "\n ...\n")
5650
printf(1, "#%x: ", start+len-15)
5660
for i = start+len-15 to start+len-1 do
5670
show_byte(i)
5680
end for
569
end if
5702
bad = 0
571
-- check post-block
5722
for i = start+len to start+len+BORDER_SPACE-1 do
57380
p = eu:peek(i)
57480
if p != trailer[1] or bad then
57540
bad += 1
57640
if bad = 1 then
5771
puts(1, "\nDATA WAS STORED ILLEGALLY, JUST AFTER THIS BLOCK:\n")
5781
puts(1, "(" & trailer[1] & " characters are OK)\n")
5791
printf(1, "#%x: ", i)
580
end if
58140
show_byte(i)
582
end if
58380
end for
5842
die("safe.e: show_block()")
5850
end procedure
586
58710
588
-- Check all allocated blocks for corruption of the leader and trailer areas.
589
integer n
590
ext_addr a
591
sequence block
592
59310
for i = 1 to length(safe_address_list) do
59416
block = safe_address_list[i]
59516
if block[3] >= 1 then
596
-- a block that we allocated
59716
a = block[1]
59816
n = block[2]
59916
if not equal(leader,
600
eu:peek({a-BORDER_SPACE, BORDER_SPACE})) then
6010
show_block(block)
60216
elsif not equal(trailer,
603
eu:peek({a+n, BORDER_SPACE})) then
6040
show_block(block)
605
end if
606
end if
60716
end for
60810
end procedure
609
6101
611
-- safe call - machine code must start in block that we own
6121
if safe_address(addr, 1, A_EXECUTE) then
6131
eu:call(addr)
6141
if check_calls then
6151
check_all_blocks() -- check for any corruption
616
end if
617
else
6180
die(sprintf("BAD CALL ADDRESS!!!! %d\n\n", addr))
619
end if
6201
end procedure
621
6221
6231
eu:c_proc(i, s)
6241
if check_calls then
6251
check_all_blocks()
626
end if
6271
end procedure
628
6298
630
object r
631
6328
r = eu:c_func(i, s)
6338
if check_calls then
6348
check_all_blocks()
635
end if
6368
return r
637
end function
638
6392
640
-- register an externally-acquired block of memory as being safe to use
6412
allocation_num += 1
6422
for i = 1 to length(safe_address_list) do
6438
if safe_address_list[i][BLOCK_ADDRESS] = block_addr then
6441
die("ATTEMPT TO REGISTER A NON-EXTERNAL BLOCK.")
645
end if
6467
end for
6471
safe_address_list = prepend(safe_address_list, {block_addr, block_len,
648
-allocation_num,memory_protection})
6491
end procedure
650
6512
652
-- remove an external block of memory from the safe address list
6532
for i = 1 to length(safe_address_list) do
6542
if safe_address_list[i][BLOCK_ADDRESS] = block_addr then
6552
if safe_address_list[i][ALLOC_NUMBER] >= 0 then
6561
die("ATTEMPT TO UNREGISTER A NON-EXTERNAL BLOCK")
657
end if
6581
safe_address_list = safe_address_list[1..i-1] &
659
safe_address_list[i+1..$]
6601
return
661
end if
6620
end for
6630
die("ATTEMPT TO UNREGISTER A BLOCK THAT WAS NOT REGISTERED!")
6640
end procedure
665
66640
667
ext_addr eaddr
668
-- set up an allocated block so we can check it for corruption
66940
if iaddr = 0 then
6700
die("OUT OF MEMORY!")
671
end if
67240
eu:poke(iaddr, leader)
67340
eaddr = iaddr + BORDER_SPACE
67440
eu:poke(eaddr+n, trailer)
67540
allocation_num += 1
67640
safe_address_list = prepend(safe_address_list, {eaddr, n, allocation_num, protection})
67740
return eaddr
678
end function
679
680
-- Internal use of the library only. free() calls this. It works with
681
-- only atoms and in the unSAFE implementation is different.
6828
683
-- free address a - make sure it was allocated
684
int_addr ia
685
6868
for i = 1 to length(safe_address_list) do
68723
if safe_address_list[i][BLOCK_ADDRESS] = a then
688
-- check pre and post block areas
6896
integer n
6906
ia = a-BORDER_SPACE
6916
if safe_address_list[i][ALLOC_NUMBER] <= 0 then
6920
die("ATTEMPT TO FREE A BLOCK THAT WAS NOT ALLOCATED!")
693
end if
6946
n = safe_address_list[i][BLOCK_LENGTH]
6956
if not equal(leader, eu:peek({ia, BORDER_SPACE})) then
6961
show_block(safe_address_list[i])
6975
elsif not equal(trailer, eu:peek({a+n, BORDER_SPACE})) then
6981
show_block(safe_address_list[i])
699
end if
7004
ifdef DATA_EXECUTE then
701
ifdef WINDOWS then
702
if dep_works() then
703
eu:c_func( VirtualFree_rid, { ia, n, MEM_RELEASE } )
704
else
705
eu:machine_proc(M_FREE, ia)
706
end if
707
elsedef
708
eu:machine_proc(M_FREE, ia)
709
end ifdef
710
elsedef
7114
if safe_address_list[i][BLOCK_PROT] != PAGE_READ_WRITE then
7121
die("ATTEMPT TO FREE WITH free() A BLOCK " &
713
"THAT WAS ALLOCATED BY allocate_protect()!")
714
end if
7153
eu:machine_proc(M_FREE, ia)
716
end ifdef
717
-- remove it from list
7183
safe_address_list = remove(safe_address_list, i)
7193
return
720
end if
72117
end for
7222
if bordered_address( a ) then
7230
die("ATTEMPT TO FREE USING AN ILLEGAL ADDRESS!")
724
end if
7252
end procedure
7268
FREE_RID = routine_id("deallocate")
727
728
-- Returns 1 if the DEP executing data only memory would cause an exception
7290
7300
ifdef WINDOWS then
731
return DEP_really_works
732
elsedef
7330
return 0
734
end ifdef
735
end function
736
737
export atom VirtualFree_rid
738
7392
740
integer free_succeeded
741
sequence block
742
7432
for i = 1 to length(safe_address_list) do
7442
block = safe_address_list[i]
7452
if block[BLOCK_ADDRESS] = addr then
7462
if safe_address_list[i][ALLOC_NUMBER] <= 0 then
7470
die("ATTEMPT TO FREE A BLOCK THAT WAS NOT ALLOCATED!")
748
end if
7492
integer n = safe_address_list[i][BLOCK_LENGTH]
7502
if not equal(leader, eu:peek({addr-BORDER_SPACE, BORDER_SPACE})) then
7510
show_block(safe_address_list[i])
7522
elsif not equal(trailer, eu:peek({addr+n, BORDER_SPACE})) then
7530
show_block(safe_address_list[i])
754
end if
7552
safe_address_list = remove( safe_address_list, i )
7562
exit
757
end if
7580
end for
759
7602
ifdef WINDOWS then
761
if dep_works() then
762
free_succeeded = c_func( VirtualFree_rid,
763
{ addr-BORDER_SPACE, size*wordsize, MEM_RELEASE } )
764
return
765
end if
766
end ifdef
7672
machine_proc(M_FREE, addr-BORDER_SPACE)
7682
end procedure
769
770
-- Shawn's custom stuff:
7710
7720
integer tm = 0
7730
for i = 1 to length( safe_address_list ) do
7740
tm += safe_address_list[i][BLOCK_LENGTH]
7750
end for
7760
return sprintf("""
777
Total memory allocations %10d
778
Total memory allocated %10dB""",
779
{ length(safe_address_list), tm } )
780
end function
781
7820
7830
integer tm = 0
7840
for i = 1 to length( safe_address_list ) do
7850
tm += safe_address_list[i][BLOCK_LENGTH]
7860
end for
7870
return tm
788
end function
789
7900
7910
return length( safe_address_list )
792
end function