/* * sys_exec.c - Kernel function implementing the "exec" system call. * * Authors Alain Greiner (2016,2017,2017,2019,2020) * * Copyright (c) UPMC Sorbonne Universites * * This file is part of ALMOS-MKH. * * ALMOS-MKH is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2.0 of the License. * * ALMOS-MKH is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ALMOS-MKH; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////i//////////////////////////////////////// // This static function is called twice by the sys_exec() function : // - to register the main() arguments (args) in the process structure. // - to register the environment variables (envs) in the structure. // In both cases the input is an array of NULL terminated string pointers in user space, // identified by the argument. The strings can be dispatched anywhere in // the calling user process space. The max number of envs, and the max number of args // are defined by the CONFIG_PROCESS_ARGS_NR and CONFIG_PROCESS_ENVS_MAX_NR parameters. ////////////////////////////////////////////////i//////////////////////////////////////// // Implementation Note: // Both the array of pointers and the strings themselve are stored in kernel space in one // single, dynamically allocated, kernel buffer containing an integer number of pages, // defined by the CONFIG_VMM_ENVS_SIZE and CONFIG_VMM_STACK_SIZE parameters. // These two kernel buffers contains : // - in the first bytes a fixed size kernel array of kernel pointers on the strings. // - in the following bytes the strings themselves. // The exec_info_t structure is defined in the file. ////////////////////////////////////////////////i//////////////////////////////////////// // @ is_args : [in] true if called for (args) / false if called for (envs). // @ u_pointers : [in] array of pointers on the strings (in user space). // @ exec_info : [inout] pointer on the exec_info structure. // @ return 0 if success / non-zero if too many strings or no memory. ////////////////////////////////////////////////i//////////////////////////////////////// static error_t exec_get_strings( bool_t is_args, char ** u_pointers, exec_info_t * exec_info ) { uint32_t index; // slot index in pointers array uint32_t length; // string length (in bytes) uint32_t pointers_bytes; // number of bytes to store pointers uint32_t max_index; // max size of pointers array char ** k_pointers; // base of kernel array of pointers char * k_buf_ptr; // pointer on first empty slot in strings buffer uint32_t k_buf_space; // number of bytes available in string buffer char * k_buf; // kernel buffer for both pointers & strings #if DEBUG_SYS_EXEC thread_t * this = CURRENT_THREAD; uint32_t cycle = (uint32_t)hal_get_cycles(); #endif // Allocate one block of physical memory for both the pointers and the strings if( is_args ) { k_buf = kmem_alloc( bits_log2(CONFIG_VMM_ARGS_SIZE << CONFIG_PPM_PAGE_ORDER), AF_ZERO ); pointers_bytes = (CONFIG_PROCESS_ARGS_MAX_NR + 1) * sizeof(char *); k_pointers = (char **)k_buf; k_buf_ptr = k_buf + pointers_bytes; k_buf_space = (CONFIG_VMM_ARGS_SIZE << CONFIG_PPM_PAGE_ORDER) - pointers_bytes; max_index = CONFIG_PROCESS_ARGS_MAX_NR + 1; #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) printk("\n[%s] thread[%x,%x] for args / u_buf %x / k_buf %x\n", __FUNCTION__, this->process->pid, this->trdid, u_pointers, k_buf ); #endif } else // envs { k_buf = kmem_alloc( bits_log2(CONFIG_VMM_ENVS_SIZE << CONFIG_PPM_PAGE_ORDER), AF_ZERO ); pointers_bytes = (CONFIG_PROCESS_ENVS_MAX_NR + 1) * sizeof(char *); k_pointers = (char **)k_buf; k_buf_ptr = k_buf + pointers_bytes; k_buf_space = (CONFIG_VMM_ENVS_SIZE << CONFIG_PPM_PAGE_ORDER) - pointers_bytes; max_index = CONFIG_PROCESS_ENVS_MAX_NR + 1; #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) printk("\n[%s] thread[%x,%x] for envs / u_buf %x / k_buf %x\n", __FUNCTION__, this->process->pid, this->trdid, u_pointers, k_buf ); #endif } // copy the user array of pointers to kernel buffer hal_copy_from_uspace( XPTR( local_cxy , k_pointers ), u_pointers, pointers_bytes ); // WARNING : the pointers copied in the k_pointers[] array are user pointers, // after the loop below, the k_pointers[] array contains kernel pointers. #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) printk("\n[%s] thread[%x,%x] moved u_ptr array of pointers to k_ptr array\n", __FUNCTION__, this->process->pid, this->trdid ); #endif // scan kernel array of pointers to copy strings to kernel buffer for( index = 0 ; index < max_index ; index++ ) { // exit loop if (k_pointers[index] == NUll) if( k_pointers[index] == NULL ) break; // compute string length (without the NUL character) length = hal_strlen_from_uspace( k_pointers[index] ); // return error if overflow in kernel buffer if( length > k_buf_space ) return -1; // copy the string itself to kernel buffer hal_copy_from_uspace( XPTR( local_cxy , k_buf_ptr ), k_pointers[index], length + 1 ); #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) printk("\n[%s] thread[%x,%x] copied string[%d] <%s> to kernel buffer / length %d\n", __FUNCTION__, this->process->pid, this->trdid, index, k_buf_ptr, length ); #endif // replace the user pointer by a kernel pointer in the k_pointer[] array k_pointers[index] = k_buf_ptr; // increment loop variables k_buf_ptr += (length + 1); k_buf_space -= (length + 1); #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) { if( k_pointers[0] != NULL ) printk("\n[%s] thread[%x,%x] : &arg0 = %x / arg0 = <%s>\n", __FUNCTION__, this->process->pid, this->trdid, k_pointers[0], k_pointers[0] ); else printk("\n[%s] thread[%x,%x] : unexpected NULL value for &arg0\n", __FUNCTION__, this->process->pid, this->trdid ); } #endif } // end loop on index // update into exec_info structure if( is_args ) { exec_info->args_pointers = k_pointers; exec_info->args_nr = index; } else { exec_info->envs_pointers = k_pointers; exec_info->envs_buf_free = k_buf_ptr; exec_info->envs_nr = index; } #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < cycle ) printk("\n[%s] thread[%x,%x] copied %d strings to kernel buffer\n", __FUNCTION__, this->process->pid, this->trdid, index ); #endif return 0; } // end exec_get_strings() /////////////////////////////// int sys_exec( char * pathname, // .elf file pathname in user space char ** user_args, // pointer on array of process arguments in user space char ** user_envs ) // pointer on array of env variables in user space { error_t error; vseg_t * vseg; // get calling thread, process, & pid thread_t * this = CURRENT_THREAD; process_t * process = this->process; pid_t pid = process->pid; trdid_t trdid = this->trdid; assert( __FUNCTION__, (CXY_FROM_PID( pid ) == local_cxy) , "must be called in the owner cluster\n"); assert( __FUNCTION__, (LTID_FROM_TRDID( trdid ) == 0) , "must be called by the main thread\n"); assert( __FUNCTION__, (user_envs == NULL) , "environment variables not supported yet\n" ); #if DEBUG_SYS_EXEC || DEBUG_SYSCALLS_ERROR uint64_t tm_start = hal_get_cycles(); #endif // check "pathname" mapped in user space if( vmm_get_vseg( process , (intptr_t)pathname , &vseg ) ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%] / pathname pointer %x unmapped\n", __FUNCTION__, pid, trdid, pathname ); #endif this->errno = EINVAL; return -1; } // check "pathname" length if( hal_strlen_from_uspace( pathname ) >= CONFIG_VFS_MAX_PATH_LENGTH ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%x] / pathname too long\n", __FUNCTION__, pid, trdid ); #endif this->errno = ENFILE; return -1; } // check "args" mapped in user space if non NULL if( (user_args != NULL) && (vmm_get_vseg( process , (intptr_t)user_args , &vseg )) ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s for thread[%x,%] : user_args pointer %x unmapped\n", __FUNCTION__, pid, trdid, user_args ); #endif this->errno = EINVAL; return -1; } // check "envs" mapped in user space if not NULL if( (user_envs != NULL) && (vmm_get_vseg( process , (intptr_t)user_envs , &vseg )) ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%] / user_envs pointer %x unmapped\n", __FUNCTION__, pid, trdid, user_envs ); #endif this->errno = EINVAL; return -1; } // 1. copy "pathname" in kernel exec_info structure hal_strcpy_from_uspace( XPTR( local_cxy , &process->exec_info.path[0] ), pathname, CONFIG_VFS_MAX_PATH_LENGTH ); #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < (uint32_t)tm_start ) printk("\n[%s] thread[%x,%x] enter / path <%s> / args %x / envs %x / cycle %d\n", __FUNCTION__, pid, trdid, &process->exec_info.path[0], user_args, user_envs, (uint32_t)tm_start ); #endif // 2. copy "arguments" pointers & strings in process exec_info if required if( user_args != NULL ) { if( exec_get_strings( true , user_args , &process->exec_info ) ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%] cannot get arguments for <%s>\n", __FUNCTION__, pid, trdid, pathname ); #endif this->errno = EINVAL; return -1; } #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < (uint32_t)tm_start ) printk("\n[%s] thread[%x,%x] set arguments in exec_info / arg[0] = <%s>\n", __FUNCTION__, pid, trdid, process->exec_info.args_pointers[0] ); #endif } // 3. copy "environment" pointers & strings in process exec_info if required if( user_envs != NULL ) { if( exec_get_strings( false , user_envs , &process->exec_info ) ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%] cannot get env variables for <%s>\n", __FUNCTION__, pid, trdid, pathname ); #endif this->errno = EINVAL; return -1; } #if DEBUG_SYS_EXEC if( DEBUG_SYS_EXEC < (uint32_t)tm_start ) printk("\n[%s] thread[%x,%x] set envs in exec_info / env[0] = <%s>\n", __FUNCTION__, pid, trdid, process->exec_info.envs_pointers[0] ); #endif } // call relevant kernel function (no return if success) error = process_make_exec(); if( error ) { #if DEBUG_SYSCALLS_ERROR if( DEBUG_SYSCALLS_ERROR < (uint32_t)tm_start ) printk("\n[ERROR] in %s : thread[%x,%x] cannot create process <%s>\n", __FUNCTION__, pid, trdid, process->exec_info.path ); #endif this->errno = error; return -1; } return 0; } // end sys_exec()