; ; An execlet is just code and data which can be put into system space at boot ; time, and a routine to run at load time. This latter routine is gone when the ; system comes up, and is a handy place to make the other code and data known to ; the world. A common mechanism to do this is by defining a logical name. That ; is done here. I was also thinking of queueing up a lock with the information ; in the lock value block, but I'd have to queue it to the swapper process ; (since nothing else is running on the system at boot time), and ; it would have a conversion AST which would need to convert the lock to a lower ; mode to actually get the info in the to LVB. Needless to say, defining a ; logical name sounded a *lot* easier. ; ; The tough part about an execlet is the restriction on ISD's, which ends up ; yielding a linker options file which is just about as big as the execlet ; itself. ; ; PSECT attributes must be coerced to the point where there are only 5 ISD's in ; the image which have any contribution. I noticed (from analyze/image) that I ; ended up with more than 5 ISD's but the other ones had a size of 0. ; ; A lot of what I've done (in the options file) may be overkill, but it works ; and I was getting tired of rebooting my workstation. Feel free to ; search-and-destroy that which is not needed. ; ; As with a driver, it is not necessary to link an application against an ; execlet. The application will need an initialization routine to hook up the ; addresses, however. It is not necessary to be done from MACRO (although ; that's what I use in my example). I've also been able to call the routines ; from C. ; ; ; The way I build this execlet, I have some dependencies on the current Alpha ; implementation (8K pages). A well-behaved build won't have such limitations. ; ; An EXECLET is directed to be loaded via SYSMAN. I use the following command: ; $ mcr sysman sys_loadable add _local_ - ; my_execlet/load_step=sysinit/severity=warning- ; /mess="Failed to start my_execlet" ; After that, the SYSMAN data file needs to be rebuilt, as follows: ; $ @sys$update:vms$system_images ; ; After doing this, you can change your execlet all you want and reboot to your ; heart's content -- the SYSMAN thing only needs to be done once. ; .TITLE MY_EXECLET .IDENT 'V01-001' .PAGE .SBTTL External symbol definitions .LIBRARY /SYS$LIBRARY:LIB/ $ACBDEF ; AST control block definitions $DYNDEF ; Dynamic constants $LNMDEF ; Logical name definitions $PCBDEF ; Process control block definitions $PRTDEF ; Protection definitions $PRVDEF ; Privilege definitions $PSLDEF ; Process status longword definitions $SSDEF ; System status codes .PSECT PAGED_USER_DATA,4,WRT,NOEXE ; ; Data in this PSECT will live in paged system space when the system is booted. ; It will be visible from any access mode, but only modifyable from kernel mode ; ; A difficulty of execlets is how to communicate the addresses of items in the ; execlet to the world. ; A common mechanism is to create a vector of the things that need to be ; exported, and then define a logical name whose translation is the address of ; this vector. ; To see the logical name from DCL, enter the following command after the ; system boots: ; $ write sys$output - ; f$fao("!XL",f$cvui(0,32,f$trnlnm("MY_EXECLET_VECTOR",,"LNM$SYSTEM_TABLE"))) ; That will give you the address of the vector. execlet_vector_addr: .address execlet_vector ; This vector contains a version number (1) and a count of the number of items to ; export (4), followed by the addresses of the items being exported. execlet_vector: .long 1,4 .address value .address set_value .address get_value .address print_message ret_len: .long 0 .align 3 system_table: .ascid /LNM$SYSTEM_TABLE/ my_execlet_vector: .ascid /MY_EXECLET_VECTOR/ .align 3 crlnm_item_list: .word 4 .word LNM$_STRING .address execlet_vector_addr .address ret_len .long 0 term: .ascid /tt:/ .align 2 channel: .word 0 .align 3 control_string: .ascid \Process with PID !XL!/ Executing routine PRINT_IT with a PD of !XL!/ First instruction is at !XL\ .align 3 output_string: .blkb 256 output_descr: out_len: .word 256 .word ^x010E .address output_string ; ; Data in this PSECT will live in nonpaged system space when the system is ; booted. ; .PSECT $$NONPAGED_USER_DATA,13,WRT,NOEXE .align 13 value: .long 0 ; Code in this PSECT will be executed early in the boot cycle. ; If this code causes a system crash, you'll have to disable execution of user ; written executive images. This is done as follows: ; >>> B -fl 0,1 ! Boot conversational ; SYSBOOT> EXA LOAD_SYS_IMAGES ! Clear the low bit in SYSGEN parameter ; SYSBOOT> SET LOAD_SYS_IMAGES xxx ! LOAD_SYS_IMAGES ; SYSBOOT> SET WRITESYS 0 ! Disable writing SYSGEN params to file ; SYSBOOT> C ; Then fix the execlet and boot again. .psect exec$init_code, exe, wrt,pic,4 INITIALIZATION_ROUTINE init_execlet ; All the data referenced by this initialization routine stays around forever in ; paged pool. Since it is only used this once, it could very well live on the ; stack. I just found the coding easier this way, and wasn't too concerned ; about one page of paged pool. init_execlet: .call_entry ; The only thing done here is to make the vector of useful stuff available to ; the world. This is done through a logical name. $CRELNM_S - TABNAM = system_table, - LOGNAM = my_execlet_vector, - ACMODE = #PSL$C_KERNEL, - ITMLST = crlnm_item_list RET ; Code in this PSECT will live in nonpaged system space when the system is ; booted. Anyone will be able to read (and execute it), just like any other ; code someone might write. ; ; Routine SET_VALUE changes values in the data section declared earlier (which ; routine is in system space and can only be modified from kernel mode), user ; mode code needs to get there with a SYS$CMKRNL call. ; ; Routine GET_VALUE can be called with a plain CALLS, since it doesn't do ; anything that requires elevated mode -- just reads a value from the data ; section. ; ; Routine PRINT_MESSAGE needs to execute from kernel mode, so again needs to be ; accessed via a SYS$CMKRNL call. ; ; Routine PRINT_IT is queued as an AST to the process whose IPID is in VALUE in ; the data section. ; .PSECT NONPAGED_USER_CODE,4,NOWRT,EXE,PIC ; set_value: .call_entry, input= MOVL PCB$L_PID(r4), value MOVL #SS$_NORMAL,R0 RET ; Code in this PSECT will live in paged system space when the system is ; booted. Anyone will be able to read (and execute it), just like any other ; code someone might write. .PSECT PAGED_USER_CODE,4,NOWRT,EXE,PIC get_value: .call_entry MOVL value, @4(AP) MOVL #SS$_NORMAL,R0 RET print_message: .call_entry, input=, preserve= ; This routine will allocate some pool (the size of an ACB), and queue an AST to ; the process whose IPID is in the "value" location. ; The AST will print a message to the terminal associated with the process. TSTL value BEQL 88$ ; Allocate some nonpaged pool ; For EXE$ALONPAGVAR: ; Input(s): ; R1: # bytes to allocate ; Output(s): ; R0: Status -- SS$_INSFMEM if memory is not available ; R1: # bytes allocated ; R2: Address of bytes allocated ; MOVL #ACB$K_LENGTH,R1 JSB EXE$ALONPAGVAR BLBC R0,99$ MOVL R2,R5 ; Fill in an ACB in this newly allocated memory MOVAL print_it,ACB$L_AST(R5) MOVL #SS$_GPTFULL,ACB$L_ASTPRM(R5) MOVB #DYN$C_ACB,ACB$B_TYPE(R5) MOVW #ACB$S_ACBDEF,ACB$W_SIZE(R5) MOVB #PSL$C_KERNEL,ACB$B_RMOD(R5) MOVL value, ACB$L_PID(R5) MOVL #0, R2 ; Now, queue up the AST to the unsuspecting process JSB SCH$QAST 99$: RET 88$: MOVL #SS$_NONEXPR, R0 RET print_it: .call_entry, input=preserve= ; Here's the routine the unsuspecting process will execute. $ASSIGN_S - DEVNAM = term, - CHAN = channel, - ACMODE = #PSL$C_KERNEL CMPL R0, #SS$_NORMAL BNEQ 99$ MOVL print_it+8, R5 $FAO_S - CTRSTR = control_string, - OUTLEN = out_len, - OUTBUF = output_descr, - P1 = PCB$L_EPID(R4), - P2 = #print_it, - P3 = R5 CMPL R0, #SS$_NORMAL BNEQ 99$ MOVW out_len, ret_len $OUTPUT channel, ret_len, output_string CMPL R0, #SS$_NORMAL BNEQ 88$ $DASSGN_S - CHAN = channel 99$: RET 88$: PUSHL R0 $DASSGN_S - CHAN = channel POPL R0 RET .END