diff --git a/software/utils/hawkeye/arm/autocompare.sh b/software/utils/hawkeye/arm/autocompare.sh new file mode 100644 index 0000000000000000000000000000000000000000..66dfb310bbf4d36c6f25b3645ad3b11ffe4190d1 --- /dev/null +++ b/software/utils/hawkeye/arm/autocompare.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +GDB=${GDB:-/usr/bin/gdb} +program=$1 #wrf.exe +breakpoint=$2 +breakpointend=$3 + +remotebreakpoint=$4 +remotebreakpointend=$5 +x86_ip=$6 +x86_password=$7 +x86_workdir=$8 +workdir=$9 +filepath=${10} + +echo $1 $2 $3 $4 $5 +./autologgin.sh $x86_ip $x86_password " +cd $x86_workdir +rm -f ./tmp/args/* +source env.sh +./autocompare.sh $program $remotebreakpoint $remotebreakpointend $workdir $filepath +exit +" +#./autocopy.sh $x86_ip $x86_password $x86_workdir/printmd5_remote.txt ./tmp > /dev/null +./autocopy.sh $x86_ip $x86_password $x86_workdir/tmp/args/ ./tmp > /dev/null + +# grid size is different between x86 and arm +rm -f ./tmp/args/grid + +#get args +($GDB -quiet ${program} << EOF +l $breakpoint,$breakpointend +EOF +) > ./tmp/gdb.txt + +# delete the content after the ! +sed -i 's/!.*$//g' ./tmp/gdb.txt +gdbtxt=$(cat ./tmp/gdb.txt) +func=${gdbtxt#*(gdb)} +func=${func/(gdb)*} +funcname=${func#*CALL} +funcname=${funcname/(*} +funcname=$(echo $funcname | sed 's/ //g') +args_string=${func#*(} +args_string=${args_string%)*} +args_string=$(echo $args_string | sed 's/ [0-9]*//g') + +zero=0 +bracketnum=0 +argswithspace='' +for i in `seq ${#args_string}` +do + var=${args_string:$i-1:1} + if [[ $bracketnum == $zero ]] && [[ $var = ',' ]] + then + argswithspace=$argswithspace' ' + elif [[ $var = ' ' ]] || [[ $var = '&' ]] + then + argswithspace=$argswithspace + else + argswithspace=$argswithspace$var + fi + + if [[ $var == '(' ]] + then + ((bracketnum++)) + fi + if [[ $var == ')' ]] + then + ((bracketnum--)) + fi +done + +args=(${argswithspace}) +argsnum=${#args[@]} + +for(( i=0;i<${#args[@]};i++)) +do + # delete string constant args + var=${args[i]} + char1="\'" + char2="\"" + if [[ ${var:0:1} == $char1 ]] || [[ ${var:0:1} == $char2 ]] + then + unset args[i] + fi + + if [[ $var == "grid" ]] || [[ $var == "t0" ]] || [[ $var == "g" ]] || [[ $var == "p0" ]] + then + unset args[i] + fi + + + # use the value on the right of the equal. + char3="=" + if [[ $var == *$char3* ]] + then + twoargs=(${var//=/ }) + args[i]=${twoargs[1]} + fi +done + +#for var in ${args[@]} +#do +# echo $var +#done + +if [ $argsnum == $zero ] +then + echo "error:can't get any args, please enter the correct funtion line number" + exit +fi +echo "info:function name is "$funcname", args is "${args[*]} + +#loop print args md5 value +mkdir -p ./tmp/$workdir"/"$funcname + +echo "$filepath" >> printmd5.txt +echo "$funcname" >> printmd5.txt + +if [ "`ls -A ./tmp/args`" != "" ] +then + ./expect.sh ${args[*]} $program $breakpoint +fi + +rm -f ./tmp/args/* +cat printmd5.txt >> ./tmp/arm_result.txt +echo "#" >> ./tmp/arm_result.txt +mv printmd5.txt ./tmp/$workdir"/"$funcname"/"$funcname".txt" diff --git a/software/utils/hawkeye/arm/autocopy.sh b/software/utils/hawkeye/arm/autocopy.sh new file mode 100644 index 0000000000000000000000000000000000000000..37f394b5d22c2d79489f8395a8f49d1670183ec1 --- /dev/null +++ b/software/utils/hawkeye/arm/autocopy.sh @@ -0,0 +1,9 @@ +#!/usr/bin/expect + +set ip [lindex $argv 0] +set password [lindex $argv 1] +set src [lindex $argv 2] +set dst [lindex $argv 3] +spawn scp -rp root@$ip:$src $dst +expect "*password:" {send "$password\r"} +interact diff --git a/software/utils/hawkeye/arm/autofind.sh b/software/utils/hawkeye/arm/autofind.sh new file mode 100644 index 0000000000000000000000000000000000000000..8773c8835972d52b35d5c1aafb1e810f04332473 --- /dev/null +++ b/software/utils/hawkeye/arm/autofind.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +program=$1 #wrf.exe +filepath1=$2 +filepath2=$2 +num1=$3 +num2=$4 + +x86_ip=$5 +x86_password=$6 +x86_workdir=$7 +workdir=$8 + +filename1=${filepath1##*/} +filename2=${filepath2##*/} + +rm -f ./tmp/1.txt ./tmp/2.txt ./tmp/3.txt ./tmp/4.txt ./tmp/5.txt + +$(cat -n $filepath1 | head -n ${num2} | tail -n +${num1} | grep -w CALL | grep -v ! | grep -v wrf_debug > ./tmp/1.txt) +$(cat -n $filepath2 | head -n ${num2} | tail -n +${num1} | grep -w CALL | grep -v ! | grep -v wrf_debug > ./tmp/2.txt) + +while true +do + + firstline=$(sed -n "1p" ./tmp/1.txt) + if [ "$firstline" == "" ] + then + break + fi + + functionname=${firstline#*CALL} + functionname=${functionname/(*} + $(grep -wi $functionname ./tmp/1.txt | cut -f1 -dC | sed -n "1p" > ./tmp/3.txt) + $(grep -wi $functionname ./tmp/2.txt | cut -f1 -dC | sed -n "1p" > ./tmp/4.txt) + + $(paste ./tmp/3.txt ./tmp/4.txt >> ./tmp/5.txt) + $(sed -i "/$functionname/d" ./tmp/1.txt) + $(sed -i "/$functionname/d" ./tmp/2.txt) +done + +linenum=1 +while true +do + breaknum=$(sed -n "${linenum}p" ./tmp/5.txt) + if [ "$breaknum" == "" ] + then + break + fi + breaknums=(${breaknum}) + if [ "${breaknums[0]}" == "" ] || [ "${breaknums[1]}" == "" ] + then + ((linenum++)) + continue + fi + ((linenum++)) + + end0=${breaknums[0]} + while true + do + func=$(sed -n "${end0}p" $filepath1) + spec="&" + if [[ ${func} == *$spec* ]] + then + ((end0++)) + else + break + fi + done + + end1=${breaknums[1]} + while true + do + func=$(sed -n "${end1}p" $filepath2) + spec="&" + if [[ ${func} == *$spec* ]] + then + ((end1++)) + else + break + fi + done + echo $program $filename1:${breaknums[0]} $filename1:$end0 $filename2:${breaknums[1]} $filename2:$end1 $workdir + ./autocompare.sh $program $filename1:${breaknums[0]} $filename1:$end0 $filename2:${breaknums[1]} $filename2:$end1 $x86_ip $x86_password $x86_workdir $workdir $filepath1 +done + diff --git a/software/utils/hawkeye/arm/autologgin.sh b/software/utils/hawkeye/arm/autologgin.sh new file mode 100644 index 0000000000000000000000000000000000000000..97240baf8dacbeb991f3c6463bccf7307ecf6e65 --- /dev/null +++ b/software/utils/hawkeye/arm/autologgin.sh @@ -0,0 +1,12 @@ +#!/usr/bin/expect + +set ip [lindex $argv 0] +set password [lindex $argv 1] +set commands [lindex $argv 2] +spawn ssh root@$ip +expect { + "*yes/no" {send "yes\r"; exp_continue} + "*password:" {send "$password\r"} +} +expect "*#" {send "$commands"} +interact diff --git a/software/utils/hawkeye/arm/expect.sh b/software/utils/hawkeye/arm/expect.sh new file mode 100644 index 0000000000000000000000000000000000000000..6a741699e6f5f1af42c2e232eb7a2d258dfc348a --- /dev/null +++ b/software/utils/hawkeye/arm/expect.sh @@ -0,0 +1,50 @@ +#!/usr/bin/expect + +log_file ./tmp/expectlog.txt +set timeout -1 +set argsnum [lindex $argc] +set args [lrange $argv 0 $argc-3] +set progname [lindex $argv $argc-2] +set breakpoint [lindex $argv $argc-1] + +spawn gdb $progname +expect "(gdb)" {send "set height unlimited\n"} +expect "(gdb)" {send "set max-value-size unlimited\n"} +expect "(gdb)" {send "define printmd5\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call print_md5_float_all_c_(\$arg%d, sizeof(\$arg%d)/4, '\$arg%d', sizeof('\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + + +expect "(gdb)" {send "define writeargs\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call writegrid_(\$arg%d, sizeof(\$arg%d)/4, './tmp/args/\$arg%d', sizeof('./tmp/args/\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + +expect "(gdb)" {send "define readargs\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call readgrid_(\$arg%d, sizeof(\$arg%d)/4, './tmp/args/\$arg%d', sizeof('./tmp/args/\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + +expect "(gdb)" {send "b $breakpoint \n"} +expect "(gdb)" {send "r \n"} +expect "(gdb)" {send "shell echo before invoke funcion\n"} +#expect "(gdb)" {send "writeargs $args\n"} +expect "(gdb)" {send "readargs $args\n"} +expect "(gdb)" {send "printmd5 $args\n"} +expect "(gdb)" {send "n \n"} +expect "(gdb)" {send "shell echo after invoke funcion\n"} +expect "(gdb)" {send "printmd5 $args\n"} +expect "(gdb)" {send "q\n"} diff --git a/software/utils/hawkeye/arm/hawkeye.sh b/software/utils/hawkeye/arm/hawkeye.sh new file mode 100644 index 0000000000000000000000000000000000000000..96da2ecdda7e80255e2e6d0583c4b06dc2041c86 --- /dev/null +++ b/software/utils/hawkeye/arm/hawkeye.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +program=$1 #wrf.exe +srcdir=$2 +filename=$3 +num1=$4 +num2=$5 +x86_ip=$6 +x86_password=$7 +x86_workdir=$8 +workdir=$9 +workdir=${workdir:="md5"} +loop=${10} +loop=${loop:=1} + +filepath=$(find $srcdir -name $filename) +mkdir -p tmp/x86/md5 +./autofind.sh $program $filepath $num1 $num2 $x86_ip $x86_password $x86_workdir $workdir +./autocopy.sh $x86_ip $x86_password $x86_workdir/tmp/md5/* ./tmp/x86/md5 > /dev/null + +while [ $loop -gt 0 ] +do + ((loop--)) + for file in ./tmp/$workdir/* + do + if [[ $file == *".txt" ]] + then + continue + fi + echo "file:"$file + x86_file="./tmp/x86/"${file#*tmp/} + diff=$(diff $file $x86_file) + if [[ "$diff" != "" ]] + then + funcname=$(sed -n "1p" $file/*.txt) + filepath=$(grep -wrn "SUBROUTINE $funcname" --exclude="*.F" $srcdir | cut -f1-2 -d:) + if [[ "$filepath" == "" ]] + then + continue + fi + + filepath=(${filepath}) + filename=$(echo ${filepath[0]} | cut -f1 -d:) + filename=${filename##*/} + num1=$(echo ${filepath[0]} | cut -f2 -d:) + num2=$(echo ${filepath[1]} | cut -f2 -d:) + ./hawkeye.sh $program $srcdir $filename $num1 $num2 $x86_ip $x86_password $x86_workdir $workdir"/"$funcname $loop + fi + done +done + diff --git a/software/utils/hawkeye/arm/printresult.sh b/software/utils/hawkeye/arm/printresult.sh new file mode 100644 index 0000000000000000000000000000000000000000..d852d5b05f240feaa8e24872455bfc4ed8e85453 --- /dev/null +++ b/software/utils/hawkeye/arm/printresult.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +srcdir=$1 +cd tmp +for file in `find md5 -name "*.txt"` +do + if [[ $file != *".txt" ]] + then + continue + fi + filediff=$(diff $file "./x86/"$file) + if [[ $filediff != "" ]] + then + funcname=$(sed -n "1p" $file) + filepath=$(grep -wrn "SUBROUTINE $funcname" --exclude="*.F" $srcdir | cut -f1-2 -d:) + if [[ "$filepath" == "" ]] + then + continue + fi + test="/" + filetmp='' + for i in `seq ${#file}` + do + var=${file:$i-1:1} + if [[ $var == $test ]] + then + filetmp=$filetmp' ' + else + filetmp=$filetmp$var + fi + done + args=(${filetmp}) + argsnum=${#filetmp[@]} + call='' + for(( i=1;i<${#args[@]}-1;i++)) + do + if [[ $call == '' ]] + then + call=${args[i]} + else + call=$call" --> "${args[i]} + fi + done + + filepath=(${filepath}) + echo "存在精度差异的函数:"$funcname "("${filepath[0]}")" + echo "函数调用关系:"$call + echo "" + call='' + fi +done diff --git a/software/utils/hawkeye/lib/aarch64/libprint_md5.so b/software/utils/hawkeye/lib/aarch64/libprint_md5.so new file mode 100644 index 0000000000000000000000000000000000000000..ce01eefafdceabbb8f11c9e8f1d09c208d5a2c00 Binary files /dev/null and b/software/utils/hawkeye/lib/aarch64/libprint_md5.so differ diff --git a/software/utils/hawkeye/lib/x86/libprint_md5.so b/software/utils/hawkeye/lib/x86/libprint_md5.so new file mode 100644 index 0000000000000000000000000000000000000000..6e84d8eb93d9fd80075b4f51b958a2112bef56f4 Binary files /dev/null and b/software/utils/hawkeye/lib/x86/libprint_md5.so differ diff --git a/software/utils/hawkeye/readme.txt b/software/utils/hawkeye/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..5d4948c36e67b83ffb81c45819ffe81d429cfde9 --- /dev/null +++ b/software/utils/hawkeye/readme.txt @@ -0,0 +1,31 @@ +本工具适用于对比查找arm平台和x86平台精度差异,指导开发人员修复精度不一致问题。 +本工具原理架构可参考《鲲鹏计算精度白皮书》: +https://bbs.huaweicloud.com/forum/thread-176980-1-1.html + +工具使用条件约束: +1.本工具使用到GDB调试功能和依赖二进制中的符号表,需要待分析应用编译选项优化等级改至-O0和增加-g选项。 +2.本工具只支持单线程运行,且不支持openMP。 + + +环境要求: +1.需要arm服务器和x86服务器各一台。 +2.服务器上需要安装gdb(V10.2)和expect。 + + +工具使用方法: +1.修改编译选项:编译优化等级更改为-O0,增加-g,删除-fopenmp、-qopenmp等并行编译选项。 +2.编译print_md5.c为print_md5.o,修改应用编译脚本,将print_md5.o文件链接进目标应用。 +3.将arm文件夹下的文件放在arm服务器上的应用、算例的同一目录下,将x86文件夹下的文件放在x86服务器上的应用、算例的同一目录下 +4.在arm服务器下执行./hawkeys.sh program srcdir filename num1 num2 x86_ip x86_password x86_workdir +program:应用二进制名称 +srcdir: 应用源码目录 +filename:待检测精度的代码文件名称 +num1:待检测精度的代码文件开始行号 +num2:待检测精度的代码文件结束行号 +x86_ip:x86服务器ip +x86_password:默认使用root账号,x86服务器root密码 +x86_workdir:x86服务器下的运行目录 + +5.arm和x86运行结果对比输出: +1.在arm服务器下执行./printresult.sh srcdir +srcdir: 应用源码目录 diff --git a/software/utils/hawkeye/x86/autocompare.sh b/software/utils/hawkeye/x86/autocompare.sh new file mode 100644 index 0000000000000000000000000000000000000000..c15095202bbc2e2e32cb08810e60919a2bfedf80 --- /dev/null +++ b/software/utils/hawkeye/x86/autocompare.sh @@ -0,0 +1,105 @@ +#!/bin/sh +#!/usr/bin/expect + +GDB=${GDB:-/usr/bin/gdb} +program=$1 #wrf.exe +breakpoint=$2 +breakpointend=$3 +workdir=$4 +filepath=$5 + +mkdir -p tmp +#get args +($GDB -quiet ${program} << EOF +l $breakpoint,$breakpointend +EOF +) > ./tmp/gdb.txt + +# delete the content after the & +sed -i 's/!.*$//g' ./tmp/gdb.txt +gdbtxt=$(cat ./tmp/gdb.txt) +func=${gdbtxt#*(gdb)} +func=${func/(gdb)*} +funcname=${func#*CALL} +funcname=${funcname/(*} +funcname=$(echo $funcname | sed 's/ //g') +args_string=${func#*(} +args_string=${args_string%)*} +args_string=$(echo $args_string | sed 's/ [0-9]*//g') + +zero=0 +bracketnum=0 +argswithspace='' +for i in `seq ${#args_string}` +do + var=${args_string:$i-1:1} + if [[ $bracketnum == $zero ]] && [[ $var = ',' ]] + then + argswithspace=$argswithspace' ' + elif [[ $var = ' ' ]] || [[ $var = '&' ]] + then + argswithspace=$argswithspace + else + argswithspace=$argswithspace$var + fi + + if [[ $var == '(' ]] + then + ((bracketnum++)) + fi + if [[ $var == ')' ]] + then + ((bracketnum--)) + fi +done + +args=(${argswithspace}) +argsnum=${#args[@]} + +for(( i=0;i<${#args[@]};i++)) +do + # delete string constant args + var=${args[i]} + char1="\'" + char2="\"" + if [[ ${var:0:1} == $char1 ]] || [[ ${var:0:1} == $char2 ]] + then + unset args[i] + fi + + if [[ $var == "grid" ]] || [[ $var == "t0" ]] || [[ $var == "g" ]] || [[ $var == "p0" ]] + then + unset args[i] + fi + + + # use the value on the right of the equal. + char3="=" + if [[ $var == *$char3* ]] + then + twoargs=(${var//=/ }) + args[i]=${twoargs[1]} + fi +done + +#for var in ${args[@]} +#do +# echo $var +#done + +if [ $argsnum == $zero ] +then + echo "error:can't get any args, please enter the correct funtion line number" + exit +fi +echo "info:function name is "$funcname", args is "${args[*]} + +#loop print args md5 value +mkdir -p ./tmp/$workdir"/"$funcname +mkdir -p ./tmp/args +echo "$filepath" >> printmd5.txt +echo "$funcname" >> printmd5.txt +./expect.sh ${args[*]} $program $breakpoint +cat printmd5.txt >> ./tmp/x86_result.txt +echo "#" >> ./tmp/x86_result.txt +mv printmd5.txt ./tmp/$workdir"/"$funcname"/"$funcname".txt" diff --git a/software/utils/hawkeye/x86/env.sh b/software/utils/hawkeye/x86/env.sh new file mode 100644 index 0000000000000000000000000000000000000000..c0be7de83e1cee16b368721d39e434292dca6aa0 --- /dev/null +++ b/software/utils/hawkeye/x86/env.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Configuring Environment Variables for the program Running of the x86 Server + +# wrf Environment Variables: +#cd /path/to/IntelOneAPI-install +#source setvars.sh +#export WRFIO_NCD_LARGE_FILE_SUPPORT=1 +#export NETCDF=/path/to/netcdf/ +#export HDF5=/path/to/hdf5/ +#export PNETCDF=/path/to/pnetcdf/ +#export CPPFLAGS="-I$HDF5/include -I$PNETCDF/include -I$NETCDF/include" +#export LDFLAGS="-L$HDF5/lib -L$PNETCDF/lib -L$NETCDF/lib -lnetcdf -lnetcdff -lpnetcdf -lhdf5_hl -lhdf5 -lz" + +#export LD_LIBRARY_PATH=/path/to/hdf5/lib/:/path/to/netcdf/lib/:/path/to/pnetcdf/lib/:$LD_LIBRARY_PATH +#export I_MPI_OFI_PROVIDER=shm +#cd - + diff --git a/software/utils/hawkeye/x86/expect.sh b/software/utils/hawkeye/x86/expect.sh new file mode 100644 index 0000000000000000000000000000000000000000..80bff7d74cb6e7949f9c55bc71cae28c179b35e8 --- /dev/null +++ b/software/utils/hawkeye/x86/expect.sh @@ -0,0 +1,50 @@ +#!/usr/bin/expect + +log_file ./tmp/expectlog.txt +set timeout -1 +set argsnum [lindex $argc] +set args [lrange $argv 0 $argc-3] +set progname [lindex $argv $argc-2] +set breakpoint [lindex $argv $argc-1] + +spawn gdb $progname +expect "(gdb)" {send "set height unlimited\n"} +expect "(gdb)" {send "set max-value-size unlimited\n"} +expect "(gdb)" {send "define printmd5\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call print_md5_float_all_c_(\$arg%d, sizeof(\$arg%d)/4, '\$arg%d', sizeof('\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + + +expect "(gdb)" {send "define writeargs\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call writegrid_(\$arg%d, sizeof(\$arg%d)/4, './tmp/args/\$arg%d', sizeof('./tmp/args/\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + +expect "(gdb)" {send "define readargs\n"} +expect ">" {send "set \$i=\$argc \n"} +expect ">" {send "while \$i\n"} +expect ">" {send "set \$index=\$argc-\$i\n"} +expect ">" {send "eval \"call readgrid_(\$arg%d, sizeof(\$arg%d)/4, './tmp/args/\$arg%d', sizeof('./tmp/args/\$arg%d'))\", \$index, \$index, \$index, \$index\n"} +expect ">" {send "set \$i=\$i-1\n"} +expect ">" {send "end\n"} +expect ">" {send "end\n"} + +expect "(gdb)" {send "b $breakpoint \n"} +expect "(gdb)" {send "r \n"} +expect "(gdb)" {send "shell echo before invoke funcion\n"} +expect "(gdb)" {send "writeargs $args\n"} +#expect "(gdb)" {send "readargs $args\n"} +expect "(gdb)" {send "printmd5 $args\n"} +expect "(gdb)" {send "n \n"} +expect "(gdb)" {send "shell echo after invoke funcion\n"} +expect "(gdb)" {send "printmd5 $args\n"} +expect "(gdb)" {send "q\n"}